diff --git a/.eslintrc.js b/.eslintrc.js index da5dfb4eccb5d..c36e8b5e7e668 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -897,7 +897,14 @@ module.exports = { ], }, }, - + // Profiling + { + files: ['x-pack/plugins/profiling/**/*.{js,mjs,ts,tsx}'], + rules: { + 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks + 'react-hooks/exhaustive-deps': ['error', { additionalHooks: '^(useAsync)$' }], + }, + }, { // disable imports from legacy uptime plugin files: ['x-pack/plugins/synthetics/public/apps/synthetics/**/*.{js,mjs,ts,tsx}'], @@ -1306,7 +1313,7 @@ module.exports = { { // typescript for front and back end files: [ - 'x-pack/plugins/{alerting,stack_alerts,actions,task_manager,event_log}/**/*.{ts,tsx}', + 'x-pack/plugins/{alerting,stack_alerts,stack_connectors,actions,task_manager,event_log}/**/*.{ts,tsx}', ], rules: { '@typescript-eslint/no-explicit-any': 'error', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0a6577ce46c68..1b6ace5538262 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -158,6 +158,9 @@ x-pack/examples/files_example @elastic/kibana-app-services /x-pack/plugins/apm/public/components/app/rum_dashboard @elastic/uptime /x-pack/test/apm_api_integration/tests/csm/ @elastic/uptime +# Profiling +/x-pack/plugins/profiling @elastic/profiling-ui + # Observability onboarding tour /x-pack/plugins/observability/public/components/shared/tour @elastic/platform-onboarding /x-pack/test/functional/apps/infra/tour.ts @elastic/platform-onboarding @@ -323,6 +326,9 @@ x-pack/examples/files_example @elastic/kibana-app-services /x-pack/plugins/actions/ @elastic/response-ops /x-pack/plugins/event_log/ @elastic/response-ops /x-pack/plugins/task_manager/ @elastic/response-ops +/x-pack/plugins/stack_connectors/ @elastic/response-ops +/x-pack/plugins/stack_connectors/server/connector_types/stack/ @elastic/response-ops-execution +/x-pack/plugins/stack_connectors/server/connector_types/cases/ @elastic/response-ops-cases /x-pack/test/alerting_api_integration/ @elastic/response-ops /x-pack/test/plugin_api_integration/test_suites/task_manager/ @elastic/response-ops /x-pack/plugins/triggers_actions_ui/ @elastic/response-ops @@ -362,6 +368,10 @@ x-pack/examples/files_example @elastic/kibana-app-services /x-pack/plugins/ingest_pipelines/ @elastic/platform-deployment-management #CC# /x-pack/plugins/cross_cluster_replication/ @elastic/platform-deployment-management +# Management Experience - Platform Onboarding +/src/plugins/guided_onboarding/ @elastic/platform-onboarding +/examples/guided_onboarding_example/ @elastic/platform-onboarding + # Security Solution /x-pack/test/endpoint_api_integration_no_ingest/ @elastic/security-solution /x-pack/test/security_solution_endpoint/ @elastic/security-solution @@ -655,6 +665,7 @@ packages/analytics/shippers/elastic_v3/browser @elastic/kibana-core packages/analytics/shippers/elastic_v3/common @elastic/kibana-core packages/analytics/shippers/elastic_v3/server @elastic/kibana-core packages/analytics/shippers/fullstory @elastic/kibana-core +packages/content-management/table_list @elastic/shared-ux packages/core/analytics/core-analytics-browser @elastic/kibana-core packages/core/analytics/core-analytics-browser-internal @elastic/kibana-core packages/core/analytics/core-analytics-browser-mocks @elastic/kibana-core diff --git a/.i18nrc.json b/.i18nrc.json index dbe3715bc7556..20b2588ca2fab 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -7,6 +7,7 @@ "bfetch": "src/plugins/bfetch", "charts": "src/plugins/charts", "console": "src/plugins/console", + "contentManagement": "packages/content-management", "core": [ "src/core", "packages/core" diff --git a/api_docs/actions.devdocs.json b/api_docs/actions.devdocs.json index 8817d0b03543e..bf3e33e925837 100644 --- a/api_docs/actions.devdocs.json +++ b/api_docs/actions.devdocs.json @@ -1444,6 +1444,20 @@ "path": "x-pack/plugins/actions/server/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "actions", + "id": "def-server.ActionTypeExecutorOptions.configurationUtilities", + "type": "Object", + "tags": [], + "label": "configurationUtilities", + "description": [], + "signature": [ + "ActionsConfigurationUtilities" + ], + "path": "x-pack/plugins/actions/server/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -1667,171 +1681,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "actions", - "id": "def-server.ActionParamsType", - "type": "Type", - "tags": [], - "label": "ActionParamsType", - "description": [], - "signature": [ - "{ readonly subAction: \"pushToService\"; readonly subActionParams: Readonly<{} & { incident: Readonly<{} & { tags: string[] | null; title: string; description: string | null; externalId: string | null; }>; comments: Readonly<{} & { comment: string; commentId: string; }>[] | null; }>; }" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/cases_webhook/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionParamsType", - "type": "Type", - "tags": [], - "label": "ActionParamsType", - "description": [], - "signature": [ - "{ readonly message: string; readonly to: string[]; readonly subject: string; readonly cc: string[]; readonly bcc: string[]; readonly kibanaFooterLink: Readonly<{} & { path: string; text: string; }>; }" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/email.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionParamsType", - "type": "Type", - "tags": [], - "label": "ActionParamsType", - "description": [], - "signature": [ - "{ readonly documents: Record[]; readonly indexOverride: string | null; }" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/es_index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionParamsType", - "type": "Type", - "tags": [], - "label": "ActionParamsType", - "description": [], - "signature": [ - "{ readonly source?: string | undefined; readonly summary?: string | undefined; readonly group?: string | undefined; readonly component?: string | undefined; readonly timestamp?: string | undefined; readonly eventAction?: \"resolve\" | \"trigger\" | \"acknowledge\" | undefined; readonly dedupKey?: string | undefined; readonly severity?: \"error\" | \"warning\" | \"info\" | \"critical\" | undefined; readonly class?: string | undefined; }" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionParamsType", - "type": "Type", - "tags": [], - "label": "ActionParamsType", - "description": [], - "signature": [ - "{ readonly message: string; readonly level: \"error\" | \"info\" | \"debug\" | \"trace\" | \"warn\" | \"fatal\"; }" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/server_log.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionParamsType", - "type": "Type", - "tags": [], - "label": "ActionParamsType", - "description": [], - "signature": [ - "{ readonly message: string; }" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/slack.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionParamsType", - "type": "Type", - "tags": [], - "label": "ActionParamsType", - "description": [], - "signature": [ - "{ readonly body?: string | undefined; }" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/webhook.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionParamsType", - "type": "Type", - "tags": [], - "label": "ActionParamsType", - "description": [], - "signature": [ - "Readonly<{} & { subAction: \"getFields\"; subActionParams: Readonly<{} & {}>; }> | Readonly<{} & { subAction: \"getIncident\"; subActionParams: Readonly<{} & { externalId: string; }>; }> | Readonly<{} & { subAction: \"handshake\"; subActionParams: Readonly<{} & {}>; }> | Readonly<{} & { subAction: \"pushToService\"; subActionParams: Readonly<{} & { incident: Readonly<{} & { description: string | null; category: string | null; externalId: string | null; severity: string | null; urgency: string | null; impact: string | null; short_description: string; subcategory: string | null; correlation_id: string | null; correlation_display: string | null; }>; comments: Readonly<{} & { comment: string; commentId: string; }>[] | null; }>; }> | Readonly<{} & { subAction: \"getChoices\"; subActionParams: Readonly<{} & { fields: string[]; }>; }> | Readonly<{} & { subAction: \"getFields\"; subActionParams: Readonly<{} & {}>; }> | Readonly<{} & { subAction: \"getIncident\"; subActionParams: Readonly<{} & { externalId: string; }>; }> | Readonly<{} & { subAction: \"handshake\"; subActionParams: Readonly<{} & {}>; }> | Readonly<{} & { subAction: \"pushToService\"; subActionParams: Readonly<{} & { incident: Readonly<{} & { description: string | null; category: string | null; externalId: string | null; short_description: string; subcategory: string | null; correlation_id: string | null; correlation_display: string | null; dest_ip: string | string[] | null; malware_hash: string | string[] | null; malware_url: string | string[] | null; source_ip: string | string[] | null; priority: string | null; }>; comments: Readonly<{} & { comment: string; commentId: string; }>[] | null; }>; }> | Readonly<{} & { subAction: \"getChoices\"; subActionParams: Readonly<{} & { fields: string[]; }>; }>" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionParamsType", - "type": "Type", - "tags": [], - "label": "ActionParamsType", - "description": [], - "signature": [ - "Readonly<{} & { subAction: \"getFields\"; subActionParams: Readonly<{} & {}>; }> | Readonly<{} & { subAction: \"getIncident\"; subActionParams: Readonly<{} & { externalId: string; }>; }> | Readonly<{} & { subAction: \"handshake\"; subActionParams: Readonly<{} & {}>; }> | Readonly<{} & { subAction: \"pushToService\"; subActionParams: Readonly<{} & { incident: Readonly<{} & { description: string | null; parent: string | null; summary: string; externalId: string | null; priority: string | null; issueType: string | null; labels: string[] | null; }>; comments: Readonly<{} & { comment: string; commentId: string; }>[] | null; }>; }> | Readonly<{} & { subAction: \"issueTypes\"; subActionParams: Readonly<{} & {}>; }> | Readonly<{} & { subAction: \"fieldsByIssueType\"; subActionParams: Readonly<{} & { id: string; }>; }> | Readonly<{} & { subAction: \"issues\"; subActionParams: Readonly<{} & { title: string; }>; }> | Readonly<{} & { subAction: \"issue\"; subActionParams: Readonly<{} & { id: string; }>; }>" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/jira/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionParamsType", - "type": "Type", - "tags": [], - "label": "ActionParamsType", - "description": [], - "signature": [ - "Readonly<{} & { subAction: \"getFields\"; subActionParams: Readonly<{} & {}>; }> | Readonly<{} & { subAction: \"getIncident\"; subActionParams: Readonly<{} & { externalId: string; }>; }> | Readonly<{} & { subAction: \"handshake\"; subActionParams: Readonly<{} & {}>; }> | Readonly<{} & { subAction: \"pushToService\"; subActionParams: Readonly<{} & { incident: Readonly<{} & { description: string | null; name: string; externalId: string | null; incidentTypes: number[] | null; severityCode: number | null; }>; comments: Readonly<{} & { comment: string; commentId: string; }>[] | null; }>; }> | Readonly<{} & { subAction: \"incidentTypes\"; subActionParams: Readonly<{} & {}>; }> | Readonly<{} & { subAction: \"severity\"; subActionParams: Readonly<{} & {}>; }>" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionParamsType", - "type": "Type", - "tags": [], - "label": "ActionParamsType", - "description": [], - "signature": [ - "{ readonly message: string; }" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/teams.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "actions", "id": "def-server.ActionsAuthorization", @@ -1947,186 +1796,6 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionTypeId", - "type": "string", - "tags": [], - "label": "ActionTypeId", - "description": [], - "signature": [ - "\".cases-webhook\"" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/cases_webhook/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionTypeId", - "type": "string", - "tags": [], - "label": "ActionTypeId", - "description": [], - "signature": [ - "\".email\"" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/email.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionTypeId", - "type": "string", - "tags": [], - "label": "ActionTypeId", - "description": [], - "signature": [ - "\".index\"" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/es_index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionTypeId", - "type": "string", - "tags": [], - "label": "ActionTypeId", - "description": [], - "signature": [ - "\".pagerduty\"" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionTypeId", - "type": "string", - "tags": [], - "label": "ActionTypeId", - "description": [], - "signature": [ - "\".server-log\"" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/server_log.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionTypeId", - "type": "string", - "tags": [], - "label": "ActionTypeId", - "description": [], - "signature": [ - "\".slack\"" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/slack.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionTypeId", - "type": "string", - "tags": [], - "label": "ActionTypeId", - "description": [], - "signature": [ - "\".webhook\"" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/webhook.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionTypeId", - "type": "string", - "tags": [], - "label": "ActionTypeId", - "description": [], - "signature": [ - "\".jira\"" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/jira/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionTypeId", - "type": "string", - "tags": [], - "label": "ActionTypeId", - "description": [], - "signature": [ - "\".resilient\"" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ActionTypeId", - "type": "string", - "tags": [], - "label": "ActionTypeId", - "description": [], - "signature": [ - "\".teams\"" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/teams.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ServiceNowITSMActionTypeId", - "type": "string", - "tags": [], - "label": "ServiceNowITSMActionTypeId", - "description": [], - "signature": [ - "\".servicenow\"" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-server.ServiceNowSIRActionTypeId", - "type": "string", - "tags": [], - "label": "ServiceNowSIRActionTypeId", - "description": [], - "signature": [ - "\".servicenow-sir\"" - ], - "path": "x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false } ], "objects": [], @@ -3386,89 +3055,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "actions", - "id": "def-common.SNProductsConfigValue", - "type": "Interface", - "tags": [], - "label": "SNProductsConfigValue", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "actions", - "id": "def-common.SNProductsConfigValue.table", - "type": "string", - "tags": [], - "label": "table", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.SNProductsConfigValue.appScope", - "type": "string", - "tags": [], - "label": "appScope", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.SNProductsConfigValue.useImportAPI", - "type": "boolean", - "tags": [], - "label": "useImportAPI", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.SNProductsConfigValue.importSetTable", - "type": "string", - "tags": [], - "label": "importSetTable", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.SNProductsConfigValue.commentFieldKey", - "type": "string", - "tags": [], - "label": "commentFieldKey", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.SNProductsConfigValue.appId", - "type": "string", - "tags": [], - "label": "appId", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "actions", "id": "def-common.ValidatedEmail", @@ -3560,18 +3146,6 @@ } ], "enums": [ - { - "parentPluginId": "actions", - "id": "def-common.AdditionalEmailServices", - "type": "Enum", - "tags": [], - "label": "AdditionalEmailServices", - "description": [], - "path": "x-pack/plugins/actions/common/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "actions", "id": "def-common.InvalidEmailReason", @@ -3740,36 +3314,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "actions", - "id": "def-common.DEFAULT_ALERTS_GROUPING_KEY", - "type": "string", - "tags": [], - "label": "DEFAULT_ALERTS_GROUPING_KEY", - "description": [], - "signature": [ - "\"{{rule.id}}:{{alert.id}}\"" - ], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-common.FIELD_PREFIX", - "type": "string", - "tags": [], - "label": "FIELD_PREFIX", - "description": [], - "signature": [ - "\"u_\"" - ], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "actions", "id": "def-common.INTERNAL_BASE_ACTION_API_PATH", @@ -3888,104 +3432,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "actions", - "id": "def-common.ServiceNowITOMActionTypeId", - "type": "string", - "tags": [], - "label": "ServiceNowITOMActionTypeId", - "description": [], - "signature": [ - "\".servicenow-itom\"" - ], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-common.ServiceNowITSMActionTypeId", - "type": "string", - "tags": [], - "label": "ServiceNowITSMActionTypeId", - "description": [], - "signature": [ - "\".servicenow\"" - ], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-common.serviceNowITSMTable", - "type": "string", - "tags": [], - "label": "serviceNowITSMTable", - "description": [], - "signature": [ - "\"incident\"" - ], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-common.ServiceNowSIRActionTypeId", - "type": "string", - "tags": [], - "label": "ServiceNowSIRActionTypeId", - "description": [], - "signature": [ - "\".servicenow-sir\"" - ], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-common.serviceNowSIRTable", - "type": "string", - "tags": [], - "label": "serviceNowSIRTable", - "description": [], - "signature": [ - "\"sn_si_incident\"" - ], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "actions", - "id": "def-common.SNProductsConfig", - "type": "Type", - "tags": [], - "label": "SNProductsConfig", - "description": [], - "signature": [ - "{ [x: string]: ", - { - "pluginId": "actions", - "scope": "common", - "docId": "kibActionsPluginApi", - "section": "def-common.SNProductsConfigValue", - "text": "SNProductsConfigValue" - }, - "; }" - ], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "actions", "id": "def-common.UptimeConnectorFeatureId", @@ -4159,255 +3605,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig", - "type": "Object", - "tags": [], - "label": "snExternalServiceConfig", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenow", - "type": "Object", - "tags": [], - "label": "'.servicenow'", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenow.importSetTable", - "type": "string", - "tags": [], - "label": "importSetTable", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenow.appScope", - "type": "string", - "tags": [], - "label": "appScope", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenow.table", - "type": "string", - "tags": [], - "label": "table", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenow.useImportAPI", - "type": "boolean", - "tags": [], - "label": "useImportAPI", - "description": [], - "signature": [ - "true" - ], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenow.commentFieldKey", - "type": "string", - "tags": [], - "label": "commentFieldKey", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenow.appId", - "type": "string", - "tags": [], - "label": "appId", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenowsir", - "type": "Object", - "tags": [], - "label": "'.servicenow-sir'", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenowsir.importSetTable", - "type": "string", - "tags": [], - "label": "importSetTable", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenowsir.appScope", - "type": "string", - "tags": [], - "label": "appScope", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenowsir.table", - "type": "string", - "tags": [], - "label": "table", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenowsir.useImportAPI", - "type": "boolean", - "tags": [], - "label": "useImportAPI", - "description": [], - "signature": [ - "true" - ], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenowsir.commentFieldKey", - "type": "string", - "tags": [], - "label": "commentFieldKey", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenowsir.appId", - "type": "string", - "tags": [], - "label": "appId", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenowitom", - "type": "Object", - "tags": [], - "label": "'.servicenow-itom'", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenowitom.importSetTable", - "type": "string", - "tags": [], - "label": "importSetTable", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenowitom.appScope", - "type": "string", - "tags": [], - "label": "appScope", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenowitom.table", - "type": "string", - "tags": [], - "label": "table", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenowitom.useImportAPI", - "type": "boolean", - "tags": [], - "label": "useImportAPI", - "description": [], - "signature": [ - "false" - ], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "actions", - "id": "def-common.snExternalServiceConfig..servicenowitom.commentFieldKey", - "type": "string", - "tags": [], - "label": "commentFieldKey", - "description": [], - "path": "x-pack/plugins/actions/common/servicenow_config.ts", - "deprecated": false, - "trackAdoption": false - } - ] - } - ], - "initialIsOpen": false - }, { "parentPluginId": "actions", "id": "def-common.UptimeConnectorFeature", diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index e68cfe697f67d..d48c8417ef3db 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.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 | |-------------------|-----------|------------------------|-----------------| -| 273 | 0 | 268 | 19 | +| 214 | 0 | 209 | 19 | ## Client diff --git a/api_docs/advanced_settings.devdocs.json b/api_docs/advanced_settings.devdocs.json index 04693d637ccb0..7942869bbf8cb 100644 --- a/api_docs/advanced_settings.devdocs.json +++ b/api_docs/advanced_settings.devdocs.json @@ -294,9 +294,201 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "advancedSettings", + "id": "def-public.toEditableConfig", + "type": "Function", + "tags": [], + "label": "toEditableConfig", + "description": [], + "signature": [ + "({\n def,\n name,\n value,\n isCustom,\n isOverridden,\n}: { def: ", + "PublicUiSettingsParams", + " & ", + "UserProvidedValues", + "; name: string; value: ", + "SavedObjectAttribute", + "; isCustom: boolean; isOverridden: boolean; }) => ", + "FieldSetting" + ], + "path": "src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "advancedSettings", + "id": "def-public.toEditableConfig.$1", + "type": "Object", + "tags": [], + "label": "{\n def,\n name,\n value,\n isCustom,\n isOverridden,\n}", + "description": [], + "path": "src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "advancedSettings", + "id": "def-public.toEditableConfig.$1.def", + "type": "CompoundType", + "tags": [], + "label": "def", + "description": [], + "signature": [ + "PublicUiSettingsParams", + " & ", + "UserProvidedValues", + "" + ], + "path": "src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "advancedSettings", + "id": "def-public.toEditableConfig.$1.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "advancedSettings", + "id": "def-public.toEditableConfig.$1.value", + "type": "CompoundType", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "SavedObjectAttributeSingle", + " | ", + "SavedObjectAttributeSingle", + "[]" + ], + "path": "src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "advancedSettings", + "id": "def-public.toEditableConfig.$1.isCustom", + "type": "boolean", + "tags": [], + "label": "isCustom", + "description": [], + "path": "src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "advancedSettings", + "id": "def-public.toEditableConfig.$1.isOverridden", + "type": "boolean", + "tags": [], + "label": "isOverridden", + "description": [], + "path": "src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [ + "the editable config object" + ], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "advancedSettings", + "id": "def-public.FieldState", + "type": "Interface", + "tags": [], + "label": "FieldState", + "description": [], + "path": "src/plugins/advanced_settings/public/management_app/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "advancedSettings", + "id": "def-public.FieldState.value", + "type": "Any", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "any" + ], + "path": "src/plugins/advanced_settings/public/management_app/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "advancedSettings", + "id": "def-public.FieldState.changeImage", + "type": "CompoundType", + "tags": [], + "label": "changeImage", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/advanced_settings/public/management_app/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "advancedSettings", + "id": "def-public.FieldState.loading", + "type": "CompoundType", + "tags": [], + "label": "loading", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/advanced_settings/public/management_app/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "advancedSettings", + "id": "def-public.FieldState.isInvalid", + "type": "CompoundType", + "tags": [], + "label": "isInvalid", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/advanced_settings/public/management_app/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "advancedSettings", + "id": "def-public.FieldState.error", + "type": "CompoundType", + "tags": [], + "label": "error", + "description": [], + "signature": [ + "string | null | undefined" + ], + "path": "src/plugins/advanced_settings/public/management_app/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false } ], - "interfaces": [], "enums": [], "misc": [], "objects": [], diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 948425dbf0575..b7790d2d87008 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.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 | |-------------------|-----------|------------------------|-----------------| -| 23 | 0 | 19 | 1 | +| 36 | 1 | 32 | 2 | ## Client @@ -37,3 +37,6 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for que ### Classes +### Interfaces + + diff --git a/api_docs/aiops.devdocs.json b/api_docs/aiops.devdocs.json index ec6cbc9258812..52234380bb0a6 100644 --- a/api_docs/aiops.devdocs.json +++ b/api_docs/aiops.devdocs.json @@ -43,6 +43,47 @@ ], "returnComment": [], "initialIsOpen": false + }, + { + "parentPluginId": "aiops", + "id": "def-public.LogCategorization", + "type": "Function", + "tags": [], + "label": "LogCategorization", + "description": [ + "\nLazy-wrapped LogCategorizationAppStateProps React component" + ], + "signature": [ + "(props: React.PropsWithChildren<", + "LogCategorizationAppStateProps", + ">) => JSX.Element" + ], + "path": "x-pack/plugins/aiops/public/shared_lazy_components.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "aiops", + "id": "def-public.LogCategorization.$1", + "type": "CompoundType", + "tags": [], + "label": "props", + "description": [ + "- properties specifying the data on which to run the analysis." + ], + "signature": [ + "React.PropsWithChildren<", + "LogCategorizationAppStateProps", + ">" + ], + "path": "x-pack/plugins/aiops/public/shared_lazy_components.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false } ], "interfaces": [], diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index fd7678f28e47e..0ad6026250b02 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) for q | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 7 | 0 | 0 | 1 | +| 9 | 0 | 0 | 2 | ## Client diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index c11acc2598f1b..616a00b212dee 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -44,7 +44,7 @@ "The rule to view" ], "signature": [ - "{ params: never; tags: string[]; id: string; monitoring?: ", + "{ name: string; params: never; tags: string[]; id: string; monitoring?: ", { "pluginId": "alerting", "scope": "common", @@ -52,7 +52,7 @@ "section": "def-common.RuleMonitoring", "text": "RuleMonitoring" }, - " | undefined; name: string; enabled: boolean; actions: ", + " | undefined; enabled: boolean; actions: ", { "pluginId": "alerting", "scope": "common", @@ -1819,7 +1819,7 @@ "section": "def-common.SanitizedRule", "text": "SanitizedRule" }, - ", \"tags\" | \"name\" | \"enabled\" | \"actions\" | \"throttle\" | \"consumer\" | \"schedule\" | \"createdBy\" | \"updatedBy\" | \"createdAt\" | \"updatedAt\" | \"notifyWhen\"> & { producer: string; ruleTypeId: string; ruleTypeName: string; }" + ", \"name\" | \"tags\" | \"enabled\" | \"actions\" | \"throttle\" | \"consumer\" | \"schedule\" | \"createdBy\" | \"updatedBy\" | \"createdAt\" | \"updatedAt\" | \"notifyWhen\"> & { producer: string; ruleTypeId: string; ruleTypeName: string; }" ], "path": "x-pack/plugins/alerting/server/types.ts", "deprecated": false, @@ -6193,7 +6193,7 @@ "label": "SanitizedRule", "description": [], "signature": [ - "{ params: Params; tags: string[]; id: string; monitoring?: ", + "{ name: string; params: Params; tags: string[]; id: string; monitoring?: ", { "pluginId": "alerting", "scope": "common", @@ -6201,7 +6201,7 @@ "section": "def-common.RuleMonitoring", "text": "RuleMonitoring" }, - " | undefined; name: string; enabled: boolean; actions: ", + " | undefined; enabled: boolean; actions: ", { "pluginId": "alerting", "scope": "common", @@ -6264,7 +6264,7 @@ "section": "def-common.SanitizedRule", "text": "SanitizedRule" }, - ", \"tags\" | \"name\" | \"enabled\" | \"actions\" | \"throttle\" | \"consumer\" | \"schedule\" | \"createdBy\" | \"updatedBy\" | \"createdAt\" | \"updatedAt\" | \"notifyWhen\"> & { producer: string; ruleTypeId: string; ruleTypeName: string; }" + ", \"name\" | \"tags\" | \"enabled\" | \"actions\" | \"throttle\" | \"consumer\" | \"schedule\" | \"createdBy\" | \"updatedBy\" | \"createdAt\" | \"updatedAt\" | \"notifyWhen\"> & { producer: string; ruleTypeId: string; ruleTypeName: string; }" ], "path": "x-pack/plugins/alerting/common/rule.ts", "deprecated": false, diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 6f8d1112c42f5..f76270693f647 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: 2022-09-15 +date: 2022-09-19 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 bf7c75c97bb93..b0e9d5757bffd 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -196,7 +196,7 @@ "APMPluginSetupDependencies", ") => { config$: ", "Observable", - "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; searchAggregatedTransactions: ", + "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; telemetryCollectionEnabled: boolean; metricsInterval: number; agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; }>>; getApmIndices: () => Promise<", "ApmIndicesConfig", @@ -415,7 +415,7 @@ "label": "config", "description": [], "signature": [ - "{ readonly indices: Readonly<{} & { error: string; metric: string; span: string; transaction: string; sourcemap: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: 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; sourcemap: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; readonly searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; readonly telemetryCollectionEnabled: boolean; readonly metricsInterval: number; readonly agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; }" ], @@ -781,7 +781,7 @@ "label": "APIEndpoint", "description": [], "signature": [ - "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/title\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/dependency\" | \"GET /internal/apm/services/{serviceName}/serviceNodes\" | \"GET /internal/apm/services\" | \"POST /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search\" | \"POST /api/apm/services/{serviceName}/annotation\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/sorted_and_filtered_services\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/traces/find\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"GET /internal/apm/alerts/chart_preview/transaction_error_rate\" | \"GET /internal/apm/alerts/chart_preview/transaction_duration\" | \"GET /internal/apm/alerts/chart_preview/transaction_error_count\" | \"GET /api/apm/settings/agent-configuration\" | \"GET /api/apm/settings/agent-configuration/view\" | \"DELETE /api/apm/settings/agent-configuration\" | \"PUT /api/apm/settings/agent-configuration\" | \"POST /api/apm/settings/agent-configuration/search\" | \"GET /api/apm/settings/agent-configuration/environments\" | \"GET /api/apm/settings/agent-configuration/agent_name\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps\" | \"POST /api/apm/sourcemaps\" | \"DELETE /api/apm/sourcemaps/{id}\" | \"GET /internal/apm/fleet/has_apm_policies\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/fleet/java_agent_versions\" | \"GET /internal/apm/dependencies/top_dependencies\" | \"GET /internal/apm/dependencies/upstream_services\" | \"GET /internal/apm/dependencies/metadata\" | \"GET /internal/apm/dependencies/charts/latency\" | \"GET /internal/apm/dependencies/charts/throughput\" | \"GET /internal/apm/dependencies/charts/error_rate\" | \"GET /internal/apm/dependencies/operations\" | \"GET /internal/apm/dependencies/charts/distribution\" | \"GET /internal/apm/dependencies/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"POST /internal/apm/correlations/field_stats/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys\" | \"GET /internal/apm/storage_explorer\" | \"GET /internal/apm/services/{serviceName}/storage_details\" | \"GET /internal/apm/storage_chart\" | \"GET /internal/apm/storage_explorer/privileges\" | \"GET /internal/apm/storage_explorer_summary_stats\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\"" + "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/title\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/dependency\" | \"GET /internal/apm/services/{serviceName}/serviceNodes\" | \"GET /internal/apm/services\" | \"POST /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search\" | \"POST /api/apm/services/{serviceName}/annotation\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/sorted_and_filtered_services\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/traces/find\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"GET /internal/apm/alerts/chart_preview/transaction_error_rate\" | \"GET /internal/apm/alerts/chart_preview/transaction_duration\" | \"GET /internal/apm/alerts/chart_preview/transaction_error_count\" | \"GET /api/apm/settings/agent-configuration\" | \"GET /api/apm/settings/agent-configuration/view\" | \"DELETE /api/apm/settings/agent-configuration\" | \"PUT /api/apm/settings/agent-configuration\" | \"POST /api/apm/settings/agent-configuration/search\" | \"GET /api/apm/settings/agent-configuration/environments\" | \"GET /api/apm/settings/agent-configuration/agent_name\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps\" | \"POST /api/apm/sourcemaps\" | \"DELETE /api/apm/sourcemaps/{id}\" | \"GET /internal/apm/fleet/has_apm_policies\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/fleet/java_agent_versions\" | \"GET /internal/apm/dependencies/top_dependencies\" | \"GET /internal/apm/dependencies/upstream_services\" | \"GET /internal/apm/dependencies/metadata\" | \"GET /internal/apm/dependencies/charts/latency\" | \"GET /internal/apm/dependencies/charts/throughput\" | \"GET /internal/apm/dependencies/charts/error_rate\" | \"GET /internal/apm/dependencies/operations\" | \"GET /internal/apm/dependencies/charts/distribution\" | \"GET /internal/apm/dependencies/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"POST /internal/apm/correlations/field_stats/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys\" | \"GET /internal/apm/storage_explorer\" | \"GET /internal/apm/services/{serviceName}/storage_details\" | \"GET /internal/apm/storage_chart\" | \"GET /internal/apm/storage_explorer/privileges\" | \"GET /internal/apm/storage_explorer_summary_stats\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\" | \"GET /internal/apm/settings/labs\"" ], "path": "x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts", "deprecated": false, @@ -811,7 +811,7 @@ "label": "APMConfig", "description": [], "signature": [ - "{ readonly indices: Readonly<{} & { error: string; metric: string; span: string; transaction: string; sourcemap: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: 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; sourcemap: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; readonly searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; readonly telemetryCollectionEnabled: boolean; readonly metricsInterval: number; readonly agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; }" ], @@ -828,7 +828,7 @@ "label": "ApmIndicesConfigName", "description": [], "signature": [ - "\"error\" | \"metric\" | \"span\" | \"transaction\" | \"sourcemap\" | \"onboarding\"" + "\"metric\" | \"error\" | \"span\" | \"transaction\" | \"sourcemap\" | \"onboarding\"" ], "path": "x-pack/plugins/apm/server/index.ts", "deprecated": false, @@ -843,7 +843,19 @@ "label": "APMServerRouteRepository", "description": [], "signature": [ - "{ \"GET /internal/apm/time_range_metadata\": ", + "{ \"GET /internal/apm/settings/labs\": ", + "ServerRoute", + "<\"GET /internal/apm/settings/labs\", undefined, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { labsItems: string[]; }, ", + "APMRouteCreateOptions", + ">; \"GET /internal/apm/time_range_metadata\": ", "ServerRoute", "<\"GET /internal/apm/time_range_metadata\", ", "TypeC", @@ -2739,7 +2751,7 @@ "section": "def-server.APMRouteHandlerResources", "text": "APMRouteHandlerResources" }, - ", { apmIndexSettings: { configurationName: \"error\" | \"metric\" | \"span\" | \"transaction\" | \"sourcemap\" | \"onboarding\"; defaultValue: string; savedValue: string | undefined; }[]; }, ", + ", { apmIndexSettings: { configurationName: \"metric\" | \"error\" | \"span\" | \"transaction\" | \"sourcemap\" | \"onboarding\"; defaultValue: string; savedValue: string | undefined; }[]; }, ", "APMRouteCreateOptions", ">; \"POST /internal/apm/settings/anomaly-detection/update_to_v3\": ", "ServerRoute", @@ -5045,6 +5057,8 @@ "PartialC", "<{ serviceNodeName: ", "StringC", + "; serviceRuntimeName: ", + "StringC", "; }>, ", "TypeC", "<{ environment: ", @@ -5590,7 +5604,7 @@ "description": [], "signature": [ "Observable", - "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; searchAggregatedTransactions: ", + "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; telemetryCollectionEnabled: boolean; metricsInterval: number; agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; }>>" ], diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index fcdf68e91be87..2dcea78f1ea31 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: 2022-09-15 +date: 2022-09-19 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 c2373f4f599f9..5b4a383c73f2c 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: 2022-09-15 +date: 2022-09-19 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 3f663181b9beb..2dbcf0cea30e0 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: 2022-09-15 +date: 2022-09-19 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 eb110e6bd7e8c..f7721e9bcda58 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.devdocs.json b/api_docs/cases.devdocs.json index b6707b75f2054..46161fd8de3be 100644 --- a/api_docs/cases.devdocs.json +++ b/api_docs/cases.devdocs.json @@ -742,7 +742,25 @@ "section": "def-common.CommentType", "text": "CommentType" }, - ".alert; alertId: string | string[]; index: string | string[]; rule: { id: string | null; name: string | null; }; owner: string; } | { type: ", + ".alert; alertId: string | string[]; index: string | string[]; rule: { id: string | null; name: string | null; }; owner: string; } | { externalReferenceId: string; externalReferenceStorage: { type: ", + { + "pluginId": "cases", + "scope": "common", + "docId": "kibCasesPluginApi", + "section": "def-common.ExternalReferenceStorageType", + "text": "ExternalReferenceStorageType" + }, + ".elasticSearchDoc; }; externalReferenceAttachmentTypeId: string; externalReferenceMetadata: { [x: string]: ", + "JsonValue", + "; } | null; type: ", + { + "pluginId": "cases", + "scope": "common", + "docId": "kibCasesPluginApi", + "section": "def-common.CommentType", + "text": "CommentType" + }, + ".externalReference; owner: string; } | { type: ", { "pluginId": "cases", "scope": "common", @@ -1509,6 +1527,18 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "cases", + "id": "def-common.ExternalReferenceStorageType", + "type": "Enum", + "tags": [], + "label": "ExternalReferenceStorageType", + "description": [], + "path": "x-pack/plugins/cases/common/api/cases/comment.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "misc": [ @@ -1569,7 +1599,13 @@ "text": "CommentType" }, ".actions; comment: string; actions: { targets: { hostname: string; endpointId: string; }[]; type: string; }; owner: string; } & { created_at: string; created_by: { email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }; owner: string; pushed_at: string | null; pushed_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; updated_at: string | null; updated_by: ({ email: string | null | undefined; full_name: string | null | undefined; username: string | null | undefined; } & { profile_uid?: string | undefined; }) | null; }) | (({ externalReferenceId: string; externalReferenceStorage: { type: ", - "ExternalReferenceStorageType", + { + "pluginId": "cases", + "scope": "common", + "docId": "kibCasesPluginApi", + "section": "def-common.ExternalReferenceStorageType", + "text": "ExternalReferenceStorageType" + }, ".elasticSearchDoc; }; externalReferenceAttachmentTypeId: string; externalReferenceMetadata: { [x: string]: ", "JsonValue", "; } | null; type: ", @@ -1581,7 +1617,13 @@ "text": "CommentType" }, ".externalReference; owner: string; } | { externalReferenceId: string; externalReferenceStorage: { type: ", - "ExternalReferenceStorageType", + { + "pluginId": "cases", + "scope": "common", + "docId": "kibCasesPluginApi", + "section": "def-common.ExternalReferenceStorageType", + "text": "ExternalReferenceStorageType" + }, ".savedObject; soType: string; }; externalReferenceAttachmentTypeId: string; externalReferenceMetadata: { [x: string]: ", "JsonValue", "; } | null; type: ", @@ -1694,6 +1736,36 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "cases", + "id": "def-common.GENERAL_CASES_OWNER", + "type": "string", + "tags": [], + "label": "GENERAL_CASES_OWNER", + "description": [], + "signature": [ + "\"cases\"" + ], + "path": "x-pack/plugins/cases/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "cases", + "id": "def-common.OBSERVABILITY_OWNER", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_OWNER", + "description": [], + "signature": [ + "\"observability\"" + ], + "path": "x-pack/plugins/cases/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "cases", "id": "def-common.PUSH_CASES_CAPABILITY", diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index fb2caa7392a78..feaeaa1eb2f58 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; @@ -21,7 +21,7 @@ Contact [ResponseOps](https://github.com/orgs/elastic/teams/response-ops) for qu | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 84 | 0 | 68 | 29 | +| 87 | 0 | 71 | 28 | ## Client diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index a69ea3dd9b941..c23a16b997363 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: 2022-09-15 +date: 2022-09-19 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 e91e40ceb4cd8..610ff7c5905c1 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index d14779a74d0a4..3c00446801d9b 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: 2022-09-15 +date: 2022-09-19 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 e83b4f28f55f2..a531c850483d4 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index dad3ac591138f..080de25c872ce 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: 2022-09-15 +date: 2022-09-19 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 77ce268798158..9cbabddb866f2 100644 --- a/api_docs/core.devdocs.json +++ b/api_docs/core.devdocs.json @@ -15237,11 +15237,11 @@ "\nA sub-set of {@link UiSettingsParams} exposed to the client-side." ], "signature": [ - "{ options?: string[] | number[] | undefined; type?: ", + "{ name?: string | undefined; value?: unknown; description?: string | undefined; category?: string[] | undefined; options?: string[] | number[] | undefined; optionLabels?: Record | undefined; requiresPageReload?: boolean | undefined; readonly?: boolean | undefined; sensitive?: boolean | undefined; type?: ", "UiSettingsType", - " | undefined; metric?: { type: string; name: string; } | undefined; value?: unknown; description?: string | undefined; name?: string | undefined; order?: number | undefined; category?: string[] | undefined; optionLabels?: Record | undefined; requiresPageReload?: boolean | undefined; readonly?: boolean | undefined; sensitive?: boolean | undefined; deprecation?: ", + " | undefined; deprecation?: ", "DeprecationSettings", - " | undefined; }" + " | undefined; order?: number | undefined; metric?: { type: string; name: string; } | undefined; }" ], "path": "node_modules/@types/kbn__core-ui-settings-common/index.d.ts", "deprecated": false, @@ -27010,7 +27010,7 @@ "\nA {@link ElasticsearchClient | client} to be used to query the ES cluster on behalf of the Kibana internal user" ], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -27128,7 +27128,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", @@ -32152,7 +32152,7 @@ "\nA {@link ElasticsearchClient | client} to be used to query the elasticsearch cluster\non behalf of the internal Kibana user." ], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -32270,7 +32270,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", @@ -33356,7 +33356,7 @@ "\nA {@link ElasticsearchClient | client} to be used to query the elasticsearch cluster\non behalf of the user that initiated the request to the Kibana server." ], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -33474,7 +33474,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", @@ -35381,7 +35381,7 @@ "label": "options", "description": [], "signature": [ - "Method extends \"get\" | \"options\" ? Required, \"body\">> : Required<", "RouteConfigOptions", @@ -35839,7 +35839,7 @@ "label": "level", "description": [], "signature": [ - "\"error\" | \"all\" | \"info\" | \"debug\" | \"off\" | \"trace\" | \"warn\" | \"fatal\"" + "\"error\" | \"all\" | \"info\" | \"debug\" | \"off\" | \"warn\" | \"trace\" | \"fatal\"" ], "path": "node_modules/@types/kbn__core-logging-server/index.d.ts", "deprecated": false, @@ -37894,7 +37894,7 @@ "label": "internalClient", "description": [], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -38012,7 +38012,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", @@ -39520,7 +39520,7 @@ "\nAdditional body options {@link RouteConfigOptionsBody}." ], "signature": [ - "(Method extends \"get\" | \"options\" ? undefined : ", + "(Method extends \"options\" | \"get\" ? undefined : ", "RouteConfigOptionsBody", ") | undefined" ], @@ -39538,7 +39538,7 @@ "\nDefines per-route timeouts." ], "signature": [ - "{ payload?: (Method extends \"get\" | \"options\" ? undefined : number) | undefined; idleSocket?: number | undefined; } | undefined" + "{ payload?: (Method extends \"options\" | \"get\" ? undefined : number) | undefined; idleSocket?: number | undefined; } | undefined" ], "path": "node_modules/@types/kbn__core-http-server/index.d.ts", "deprecated": false, @@ -49921,7 +49921,7 @@ "\nClient used to query the elasticsearch cluster.\n" ], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -50039,7 +50039,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", @@ -51623,7 +51623,7 @@ "\nRoute options: If 'GET' or 'OPTIONS' method, body options won't be returned." ], "signature": [ - "Method extends \"get\" | \"options\" ? Required, \"body\">> : Required<", "RouteConfigOptions", @@ -52418,11 +52418,11 @@ "\nA sub-set of {@link UiSettingsParams} exposed to the client-side." ], "signature": [ - "{ options?: string[] | number[] | undefined; type?: ", + "{ name?: string | undefined; value?: unknown; description?: string | undefined; category?: string[] | undefined; options?: string[] | number[] | undefined; optionLabels?: Record | undefined; requiresPageReload?: boolean | undefined; readonly?: boolean | undefined; sensitive?: boolean | undefined; type?: ", "UiSettingsType", - " | undefined; metric?: { type: string; name: string; } | undefined; value?: unknown; description?: string | undefined; name?: string | undefined; order?: number | undefined; category?: string[] | undefined; optionLabels?: Record | undefined; requiresPageReload?: boolean | undefined; readonly?: boolean | undefined; sensitive?: boolean | undefined; deprecation?: ", + " | undefined; deprecation?: ", "DeprecationSettings", - " | undefined; }" + " | undefined; order?: number | undefined; metric?: { type: string; name: string; } | undefined; }" ], "path": "node_modules/@types/kbn__core-ui-settings-common/index.d.ts", "deprecated": false, @@ -52923,7 +52923,7 @@ "\nSet of HTTP methods not changing the state of the server." ], "signature": [ - "\"get\" | \"options\"" + "\"options\" | \"get\"" ], "path": "node_modules/@types/kbn__core-http-server/index.d.ts", "deprecated": false, diff --git a/api_docs/core.mdx b/api_docs/core.mdx index 43bd29d121fb9..e79d69afe2deb 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] --- import coreObj from './core.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index a5824dcb16327..45a19d534d16a 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: 2022-09-15 +date: 2022-09-19 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 e15e706dabf6f..9d091d4b46aab 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: 2022-09-15 +date: 2022-09-19 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 f18de69b55347..2fcb802179500 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: 2022-09-15 +date: 2022-09-19 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 74700ab870fde..27afaa93906f5 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -8612,7 +8612,7 @@ "label": "AggConfigOptions", "description": [], "signature": [ - "{ type: ", + "{ schema?: string | undefined; type: ", { "pluginId": "data", "scope": "common", @@ -8622,7 +8622,7 @@ }, "; params?: {} | ", "SerializableRecord", - " | undefined; id?: string | undefined; enabled?: boolean | undefined; schema?: string | undefined; }" + " | undefined; id?: string | undefined; enabled?: boolean | undefined; }" ], "path": "src/plugins/data/common/search/aggs/agg_config.ts", "deprecated": false, @@ -15425,7 +15425,7 @@ "label": "elasticsearchClient", "description": [], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -15543,7 +15543,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", diff --git a/api_docs/data.mdx b/api_docs/data.mdx index ce3aae4debcd8..0f32f3ecebcbf 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index f0fbe18c567d0..d3ba9f3fb8120 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.devdocs.json b/api_docs/data_search.devdocs.json index a4b47f09c07b1..67aec61aeedf4 100644 --- a/api_docs/data_search.devdocs.json +++ b/api_docs/data_search.devdocs.json @@ -27979,7 +27979,7 @@ "label": "AggConfigOptions", "description": [], "signature": [ - "{ type: ", + "{ schema?: string | undefined; type: ", { "pluginId": "data", "scope": "common", @@ -27989,7 +27989,7 @@ }, "; params?: {} | ", "SerializableRecord", - " | undefined; id?: string | undefined; enabled?: boolean | undefined; schema?: string | undefined; }" + " | undefined; id?: string | undefined; enabled?: boolean | undefined; }" ], "path": "src/plugins/data/common/search/aggs/agg_config.ts", "deprecated": false, @@ -28841,7 +28841,7 @@ "label": "CreateAggConfigParams", "description": [], "signature": [ - "{ type: string | ", + "{ schema?: string | undefined; type: string | ", { "pluginId": "data", "scope": "common", @@ -28851,7 +28851,7 @@ }, "; params?: {} | ", "SerializableRecord", - " | undefined; id?: string | undefined; enabled?: boolean | undefined; schema?: string | undefined; }" + " | undefined; id?: string | undefined; enabled?: boolean | undefined; }" ], "path": "src/plugins/data/common/search/aggs/agg_configs.ts", "deprecated": false, diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 8574dc75b0e1e..4374fd3bfa996 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 73df9cb6ab875..8d75cc2718001 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 79eb8525bbeb0..84bb8bca2a35b 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index 2f98f85b1c8a7..022be47d5a96d 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.devdocs.json b/api_docs/data_views.devdocs.json index 8766b1f50d97d..0ba596a1b00b6 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -11480,7 +11480,7 @@ "label": "elasticsearchClient", "description": [], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -11598,7 +11598,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", @@ -20758,7 +20758,7 @@ "label": "DataViewSavedObjectAttrs", "description": [], "signature": [ - "{ type?: string | undefined; title: string; name?: string | undefined; typeMeta?: string | undefined; }" + "{ name?: string | undefined; type?: string | undefined; title: string; typeMeta?: string | undefined; }" ], "path": "src/plugins/data_views/common/data_views/data_views.ts", "deprecated": false, diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 7ad7796e219d5..b05f4fab83e7d 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 1a428db2ce689..0665713d3a486 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 6d8db8d2e2352..91fd9b7dd580e 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 56ab5cae75dda..ba5af63634077 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -633,9 +633,9 @@ This is relied on by the reporting feature, and should be removed once reporting migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/issues/19914 | | | [app_authorization.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/app_authorization.ts#:~:text=getKibanaFeatures), [privileges.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.ts#:~:text=getKibanaFeatures), [authorization_service.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/authorization_service.tsx#:~:text=getKibanaFeatures), [app_authorization.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/app_authorization.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures)+ 14 more | 8.8.0 | | | [authorization_service.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/authorization_service.tsx#:~:text=getElasticsearchFeatures) | 8.8.0 | -| | [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode) | 8.8.0 | +| | [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode) | 8.8.0 | | | [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/plugin.tsx#:~:text=license%24) | 8.8.0 | -| | [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode) | 8.8.0 | +| | [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode) | 8.8.0 | | | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/plugin.ts#:~:text=license%24) | 8.8.0 | | | [logout_app.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/authentication/logout/logout_app.test.ts#:~:text=appBasePath) | 8.8.0 | | | [logout_app.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/authentication/logout/logout_app.test.ts#:~:text=onAppLeave) | 8.8.0 | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index a96c199ead60b..3c7dcbd2d9a03 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -144,9 +144,9 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ This is relied on by the reporting feature, and should be removed once reporting migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/issues/19914 | | security | | [authorization_service.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/authorization_service.tsx#:~:text=getElasticsearchFeatures) | 8.8.0 | -| security | | [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode) | 8.8.0 | +| security | | [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode) | 8.8.0 | | security | | [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/plugin.tsx#:~:text=license%24) | 8.8.0 | -| security | | [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode) | 8.8.0 | +| security | | [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode) | 8.8.0 | | security | | [logout_app.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/authentication/logout/logout_app.test.ts#:~:text=appBasePath) | 8.8.0 | | security | | [logout_app.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/authentication/logout/logout_app.test.ts#:~:text=onAppLeave) | 8.8.0 | diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index bc99737695896..cf4c7e01b4440 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: 2022-09-15 +date: 2022-09-19 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 87e0d53a57078..c9ebfdee37434 100644 --- a/api_docs/discover.devdocs.json +++ b/api_docs/discover.devdocs.json @@ -1873,6 +1873,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "discover", + "id": "def-common.SHOW_LEGACY_FIELD_TOP_VALUES", + "type": "string", + "tags": [], + "label": "SHOW_LEGACY_FIELD_TOP_VALUES", + "description": [], + "signature": [ + "\"discover:showLegacyFieldTopValues\"" + ], + "path": "src/plugins/discover/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "discover", "id": "def-common.SHOW_MULTIFIELDS", diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index bc3ac871b621f..39d6a57c4f43b 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-disco | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 95 | 0 | 78 | 4 | +| 96 | 0 | 79 | 4 | ## Client diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 2b6b482ebf275..1ae9dbbe468f3 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 0ecd141b83ecd..2891c863f698d 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 0a9dcf8931ed0..9167009b0103d 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 40d48eca5932b..383050dcd12d8 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.devdocs.json b/api_docs/enterprise_search.devdocs.json index 8ee7ecbb68c9a..b42b4016faf40 100644 --- a/api_docs/enterprise_search.devdocs.json +++ b/api_docs/enterprise_search.devdocs.json @@ -118,6 +118,21 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "enterpriseSearch", + "id": "def-server.CURRENT_CONNECTORS_INDEX", + "type": "string", + "tags": [], + "label": "CURRENT_CONNECTORS_INDEX", + "description": [], + "signature": [ + "\".elastic-connectors-v1\"" + ], + "path": "x-pack/plugins/enterprise_search/server/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [ diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 1a2227a7e3932..28683ea8873e3 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Enterprise Search](https://github.com/orgs/elastic/teams/enterprise-sea | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 8 | 0 | 8 | 0 | +| 9 | 0 | 9 | 0 | ## Server diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 20cb6d26e4a01..1c6adb7d61a93 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 1e86634d962a7..156ba659bc231 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.devdocs.json b/api_docs/event_log.devdocs.json index 491bb3f165423..f821ced3a76e0 100644 --- a/api_docs/event_log.devdocs.json +++ b/api_docs/event_log.devdocs.json @@ -1312,7 +1312,7 @@ "label": "data", "description": [], "signature": [ - "(Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ id?: string | undefined; description?: string | undefined; name?: string | undefined; version?: string | undefined; license?: string | undefined; category?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; type?: string[] | undefined; id?: string | undefined; outcome?: string | undefined; created?: string | undefined; category?: string[] | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined)[]" + "(Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; outcome?: string | undefined; created?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined)[]" ], "path": "x-pack/plugins/event_log/server/es/cluster_client_adapter.ts", "deprecated": false, @@ -1332,7 +1332,7 @@ "label": "IEvent", "description": [], "signature": [ - "DeepPartial | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ id?: string | undefined; description?: string | undefined; name?: string | undefined; version?: string | undefined; license?: string | undefined; category?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; type?: string[] | undefined; id?: string | undefined; outcome?: string | undefined; created?: string | undefined; category?: string[] | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}>>> | undefined" + "DeepPartial | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; outcome?: string | undefined; created?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}>>> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, @@ -1347,7 +1347,7 @@ "label": "IValidatedEvent", "description": [], "signature": [ - "Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ id?: string | undefined; description?: string | undefined; name?: string | undefined; version?: string | undefined; license?: string | undefined; category?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; type?: string[] | undefined; id?: string | undefined; outcome?: string | undefined; created?: string | undefined; category?: string[] | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined" + "Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; outcome?: string | undefined; created?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 7c18a607537e6..5e8b28fd6c54b 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 92217a06c7744..e6a2cc0692b41 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 9db92d98d2453..095b4f4491e56 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 22670ec4a06a0..c9ca9e05d26e5 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 929ed0d782e8f..f497ba9aa3650 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index d6a18dacfc30b..7f2fadbf83652 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index d747d97a38df0..90988b5449313 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index faa3903d56d76..5a7f301cfc038 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index c744ea5e03196..4f22aec2a208f 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index e54106977418c..bd4fead35a4bc 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index d4d1589d32b09..cd4122c979291 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 184b15afa80ed..37584f22212f3 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index b2d388331f947..36ec5f95939b0 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 2f988bc4e4ad2..2ca4734300253 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index bf795608fd9e7..b6f097c876fb7 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index a2b04fd8cc677..a87cd35e835b3 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index d755461ce9119..f40931c866756 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 63557715c45d9..bcfba3cac7aef 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.devdocs.json b/api_docs/files.devdocs.json index e8c6d117ab46a..faee578b74688 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -972,7 +972,7 @@ "\nAn elasticsearch client that will be used to interact with the cluster" ], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -1090,7 +1090,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 136829651b74d..a4158f19eddb5 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index 7512afd90c524..3cafe7115d4d9 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -12178,12 +12178,12 @@ { "parentPluginId": "fleet", "id": "def-common.PackageSpecManifest.type", - "type": "string", + "type": "CompoundType", "tags": [], "label": "type", "description": [], "signature": [ - "\"integration\" | undefined" + "\"input\" | \"integration\" | undefined" ], "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", "deprecated": false, @@ -12939,180 +12939,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "fleet", - "id": "def-common.RegistryPolicyTemplate", - "type": "Interface", - "tags": [], - "label": "RegistryPolicyTemplate", - "description": [], - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "fleet", - "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.name", - "type": "string", - "tags": [], - "label": "[RegistryPolicyTemplateKeys.name]", - "description": [], - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "fleet", - "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.title", - "type": "string", - "tags": [], - "label": "[RegistryPolicyTemplateKeys.title]", - "description": [], - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "fleet", - "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.description", - "type": "string", - "tags": [], - "label": "[RegistryPolicyTemplateKeys.description]", - "description": [], - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "fleet", - "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.icons", - "type": "Array", - "tags": [], - "label": "[RegistryPolicyTemplateKeys.icons]", - "description": [], - "signature": [ - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.RegistryImage", - "text": "RegistryImage" - }, - "[] | undefined" - ], - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "fleet", - "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.screenshots", - "type": "Array", - "tags": [], - "label": "[RegistryPolicyTemplateKeys.screenshots]", - "description": [], - "signature": [ - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.RegistryImage", - "text": "RegistryImage" - }, - "[] | undefined" - ], - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "fleet", - "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.categories", - "type": "Array", - "tags": [], - "label": "[RegistryPolicyTemplateKeys.categories]", - "description": [], - "signature": [ - "(", - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.PackageSpecCategory", - "text": "PackageSpecCategory" - }, - " | undefined)[] | undefined" - ], - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "fleet", - "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.data_streams", - "type": "Array", - "tags": [], - "label": "[RegistryPolicyTemplateKeys.data_streams]", - "description": [], - "signature": [ - "string[] | undefined" - ], - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "fleet", - "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.inputs", - "type": "Array", - "tags": [], - "label": "[RegistryPolicyTemplateKeys.inputs]", - "description": [], - "signature": [ - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.RegistryInput", - "text": "RegistryInput" - }, - "[] | undefined" - ], - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "fleet", - "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.readme", - "type": "string", - "tags": [], - "label": "[RegistryPolicyTemplateKeys.readme]", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "fleet", - "id": "def-common.RegistryPolicyTemplate.RegistryPolicyTemplateKeys.multiple", - "type": "CompoundType", - "tags": [], - "label": "[RegistryPolicyTemplateKeys.multiple]", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "x-pack/plugins/fleet/common/types/models/epm.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "fleet", "id": "def-common.RegistryStream", @@ -15021,6 +14847,23 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "fleet", + "id": "def-common.RegistryPolicyTemplate", + "type": "Type", + "tags": [], + "label": "RegistryPolicyTemplate", + "description": [], + "signature": [ + "RegistryPolicyIntegrationTemplate", + " | ", + "RegistryPolicyInputOnlyTemplate" + ], + "path": "x-pack/plugins/fleet/common/types/models/epm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "fleet", "id": "def-common.RegistrySearchResult", @@ -15029,7 +14872,7 @@ "label": "RegistrySearchResult", "description": [], "signature": [ - "{ type?: \"integration\" | undefined; path: string; download: string; title: string; description: string; icons?: (", + "{ name: string; description: string; type?: \"input\" | \"integration\" | undefined; path: string; download: string; title: string; icons?: (", { "pluginId": "fleet", "scope": "common", @@ -15053,7 +14896,7 @@ "section": "def-common.PackageSpecCategory", "text": "PackageSpecCategory" }, - " | undefined)[] | undefined; name: string; version: string; internal?: boolean | undefined; release?: \"experimental\" | \"beta\" | \"ga\" | undefined; policy_templates?: ", + " | undefined)[] | undefined; version: string; internal?: boolean | undefined; release?: \"experimental\" | \"beta\" | \"ga\" | undefined; policy_templates?: ", { "pluginId": "fleet", "scope": "common", diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 83ff56531a3f0..eadfc73e23429 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: 2022-09-15 +date: 2022-09-19 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 | |-------------------|-----------|------------------------|-----------------| -| 979 | 3 | 880 | 15 | +| 969 | 3 | 870 | 17 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 8ad414d74f23f..6354e09d87ad0 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.devdocs.json b/api_docs/guided_onboarding.devdocs.json new file mode 100644 index 0000000000000..27b0b8446a7d8 --- /dev/null +++ b/api_docs/guided_onboarding.devdocs.json @@ -0,0 +1,208 @@ +{ + "id": "guidedOnboarding", + "client": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingState", + "type": "Interface", + "tags": [], + "label": "GuidedOnboardingState", + "description": [], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingState.activeGuide", + "type": "CompoundType", + "tags": [], + "label": "activeGuide", + "description": [], + "signature": [ + { + "pluginId": "guidedOnboarding", + "scope": "public", + "docId": "kibGuidedOnboardingPluginApi", + "section": "def-public.UseCase", + "text": "UseCase" + }, + " | \"unset\"" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingState.activeStep", + "type": "string", + "tags": [], + "label": "activeStep", + "description": [], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.UseCase", + "type": "Type", + "tags": [], + "label": "UseCase", + "description": [], + "signature": [ + "\"search\" | \"security\" | \"observability\"" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [], + "setup": { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingPluginSetup", + "type": "Interface", + "tags": [], + "label": "GuidedOnboardingPluginSetup", + "description": [], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingPluginStart", + "type": "Interface", + "tags": [], + "label": "GuidedOnboardingPluginStart", + "description": [], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingPluginStart.guidedOnboardingApi", + "type": "Object", + "tags": [], + "label": "guidedOnboardingApi", + "description": [], + "signature": [ + "ApiService", + " | undefined" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "guidedOnboarding", + "id": "def-server.GuidedOnboardingPluginSetup", + "type": "Interface", + "tags": [], + "label": "GuidedOnboardingPluginSetup", + "description": [], + "path": "src/plugins/guided_onboarding/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "guidedOnboarding", + "id": "def-server.GuidedOnboardingPluginStart", + "type": "Interface", + "tags": [], + "label": "GuidedOnboardingPluginStart", + "description": [], + "path": "src/plugins/guided_onboarding/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-common.API_BASE_PATH", + "type": "string", + "tags": [], + "label": "API_BASE_PATH", + "description": [], + "signature": [ + "\"/api/guided_onboarding\"" + ], + "path": "src/plugins/guided_onboarding/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-common.PLUGIN_ID", + "type": "string", + "tags": [], + "label": "PLUGIN_ID", + "description": [], + "signature": [ + "\"guidedOnboarding\"" + ], + "path": "src/plugins/guided_onboarding/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-common.PLUGIN_NAME", + "type": "string", + "tags": [], + "label": "PLUGIN_NAME", + "description": [], + "signature": [ + "\"guidedOnboarding\"" + ], + "path": "src/plugins/guided_onboarding/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx new file mode 100644 index 0000000000000..b98c4341430a6 --- /dev/null +++ b/api_docs/guided_onboarding.mdx @@ -0,0 +1,52 @@ +--- +#### +#### This 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: kibGuidedOnboardingPluginApi +slug: /kibana-dev-docs/api/guidedOnboarding +title: "guidedOnboarding" +image: https://source.unsplash.com/400x175/?github +description: API docs for the guidedOnboarding plugin +date: 2022-09-19 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] +--- +import guidedOnboardingObj from './guided_onboarding.devdocs.json'; + +Guided onboarding framework + +Contact [Journey Onboarding](https://github.com/orgs/elastic/teams/platform-onboarding) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 12 | 0 | 12 | 1 | + +## Client + +### Setup + + +### Start + + +### Interfaces + + +### Consts, variables and types + + +## Server + +### Setup + + +### Start + + +## Common + +### Consts, variables and types + + diff --git a/api_docs/home.devdocs.json b/api_docs/home.devdocs.json index fb0744959b803..c8e980ff0a0ab 100644 --- a/api_docs/home.devdocs.json +++ b/api_docs/home.devdocs.json @@ -1738,7 +1738,7 @@ "signature": [ "{ getSampleDatasets: () => ", "Writable", - "[]; previewImagePath: string; overviewDashboard: string; defaultIndex: string; dataIndices: Readonly<{} & { id: string; fields: Record; timeFields: string[]; dataPath: string; currentTimeMarker: string; preserveDayOfWeekTimeOfDay: boolean; }>[]; }>>[]; addSavedObjectsToSampleDataset: (id: string, savedObjects: ", + "[]; previewImagePath: string; overviewDashboard: string; defaultIndex: string; dataIndices: Readonly<{} & { id: string; fields: Record; timeFields: string[]; dataPath: string; currentTimeMarker: string; preserveDayOfWeekTimeOfDay: boolean; }>[]; }>>[]; addSavedObjectsToSampleDataset: (id: string, savedObjects: ", "SavedObject", "[]) => void; addAppLinksToSampleDataset: (id: string, appLinks: ", { @@ -1773,7 +1773,7 @@ "signature": [ "() => ", "Writable", - "[]; previewImagePath: string; overviewDashboard: string; defaultIndex: string; dataIndices: Readonly<{} & { id: string; fields: Record; timeFields: string[]; dataPath: string; currentTimeMarker: string; preserveDayOfWeekTimeOfDay: boolean; }>[]; }>>" + "[]; previewImagePath: string; overviewDashboard: string; defaultIndex: string; dataIndices: Readonly<{} & { id: string; fields: Record; timeFields: string[]; dataPath: string; currentTimeMarker: string; preserveDayOfWeekTimeOfDay: boolean; }>[]; }>>" ], "path": "src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts", "deprecated": false, @@ -1830,7 +1830,7 @@ "section": "def-server.TutorialContext", "text": "TutorialContext" }, - ") => Readonly<{ isBeta?: boolean | undefined; savedObjects?: any[] | undefined; euiIconType?: string | undefined; previewImagePath?: string | undefined; moduleName?: string | undefined; completionTimeMinutes?: number | undefined; elasticCloud?: Readonly<{ params?: Readonly<{ defaultValue?: any; } & { type: \"string\" | \"number\"; id: string; label: string; }>[] | undefined; } & { instructionSets: Readonly<{ title?: string | undefined; callOut?: Readonly<{ message?: string | undefined; iconType?: string | undefined; } & { title: string; }> | undefined; statusCheck?: Readonly<{ error?: string | undefined; success?: string | undefined; text?: string | undefined; title?: string | undefined; btnLabel?: string | undefined; } & { esHitsCheck: Readonly<{} & { query: Record; index: string | string[]; }>; }> | undefined; } & { instructionVariants: Readonly<{ initialSelected?: boolean | undefined; } & { id: string; instructions: Readonly<{ title?: string | undefined; commands?: string[] | undefined; textPre?: string | undefined; textPost?: string | undefined; customComponentName?: string | undefined; } & {}>[]; }>[]; }>[]; }> | undefined; onPremElasticCloud?: Readonly<{ params?: Readonly<{ defaultValue?: any; } & { type: \"string\" | \"number\"; id: string; label: string; }>[] | undefined; } & { instructionSets: Readonly<{ title?: string | undefined; callOut?: Readonly<{ message?: string | undefined; iconType?: string | undefined; } & { title: string; }> | undefined; statusCheck?: Readonly<{ error?: string | undefined; success?: string | undefined; text?: string | undefined; title?: string | undefined; btnLabel?: string | undefined; } & { esHitsCheck: Readonly<{} & { query: Record; index: string | string[]; }>; }> | undefined; } & { instructionVariants: Readonly<{ initialSelected?: boolean | undefined; } & { id: string; instructions: Readonly<{ title?: string | undefined; commands?: string[] | undefined; textPre?: string | undefined; textPost?: string | undefined; customComponentName?: string | undefined; } & {}>[]; }>[]; }>[]; }> | undefined; artifacts?: Readonly<{ application?: Readonly<{} & { path: string; label: string; }> | undefined; exportedFields?: Readonly<{} & { documentationUrl: string; }> | undefined; } & { dashboards: Readonly<{ linkLabel?: string | undefined; } & { id: string; isOverview: boolean; }>[]; }> | undefined; savedObjectsInstallMsg?: string | undefined; customStatusCheckName?: string | undefined; integrationBrowserCategories?: string[] | undefined; eprPackageOverlap?: string | undefined; } & { id: string; name: string; category: \"other\" | \"security\" | \"metrics\" | \"logging\"; shortDescription: string; longDescription: string; onPrem: Readonly<{ params?: Readonly<{ defaultValue?: any; } & { type: \"string\" | \"number\"; id: string; label: string; }>[] | undefined; } & { instructionSets: Readonly<{ title?: string | undefined; callOut?: Readonly<{ message?: string | undefined; iconType?: string | undefined; } & { title: string; }> | undefined; statusCheck?: Readonly<{ error?: string | undefined; success?: string | undefined; text?: string | undefined; title?: string | undefined; btnLabel?: string | undefined; } & { esHitsCheck: Readonly<{} & { query: Record; index: string | string[]; }>; }> | undefined; } & { instructionVariants: Readonly<{ initialSelected?: boolean | undefined; } & { id: string; instructions: Readonly<{ title?: string | undefined; commands?: string[] | undefined; textPre?: string | undefined; textPost?: string | undefined; customComponentName?: string | undefined; } & {}>[]; }>[]; }>[]; }>; }>" + ") => Readonly<{ isBeta?: boolean | undefined; savedObjects?: any[] | undefined; euiIconType?: string | undefined; previewImagePath?: string | undefined; moduleName?: string | undefined; completionTimeMinutes?: number | undefined; elasticCloud?: Readonly<{ params?: Readonly<{ defaultValue?: any; } & { type: \"string\" | \"number\"; id: string; label: string; }>[] | undefined; } & { instructionSets: Readonly<{ title?: string | undefined; callOut?: Readonly<{ message?: string | undefined; iconType?: string | undefined; } & { title: string; }> | undefined; statusCheck?: Readonly<{ error?: string | undefined; success?: string | undefined; text?: string | undefined; title?: string | undefined; btnLabel?: string | undefined; } & { esHitsCheck: Readonly<{} & { query: Record; index: string | string[]; }>; }> | undefined; } & { instructionVariants: Readonly<{ initialSelected?: boolean | undefined; } & { id: string; instructions: Readonly<{ title?: string | undefined; commands?: string[] | undefined; textPre?: string | undefined; textPost?: string | undefined; customComponentName?: string | undefined; } & {}>[]; }>[]; }>[]; }> | undefined; onPremElasticCloud?: Readonly<{ params?: Readonly<{ defaultValue?: any; } & { type: \"string\" | \"number\"; id: string; label: string; }>[] | undefined; } & { instructionSets: Readonly<{ title?: string | undefined; callOut?: Readonly<{ message?: string | undefined; iconType?: string | undefined; } & { title: string; }> | undefined; statusCheck?: Readonly<{ error?: string | undefined; success?: string | undefined; text?: string | undefined; title?: string | undefined; btnLabel?: string | undefined; } & { esHitsCheck: Readonly<{} & { query: Record; index: string | string[]; }>; }> | undefined; } & { instructionVariants: Readonly<{ initialSelected?: boolean | undefined; } & { id: string; instructions: Readonly<{ title?: string | undefined; commands?: string[] | undefined; textPre?: string | undefined; textPost?: string | undefined; customComponentName?: string | undefined; } & {}>[]; }>[]; }>[]; }> | undefined; artifacts?: Readonly<{ application?: Readonly<{} & { path: string; label: string; }> | undefined; exportedFields?: Readonly<{} & { documentationUrl: string; }> | undefined; } & { dashboards: Readonly<{ linkLabel?: string | undefined; } & { id: string; isOverview: boolean; }>[]; }> | undefined; savedObjectsInstallMsg?: string | undefined; customStatusCheckName?: string | undefined; integrationBrowserCategories?: string[] | undefined; eprPackageOverlap?: string | undefined; } & { name: string; category: \"other\" | \"security\" | \"metrics\" | \"logging\"; id: string; shortDescription: string; longDescription: string; onPrem: Readonly<{ params?: Readonly<{ defaultValue?: any; } & { type: \"string\" | \"number\"; id: string; label: string; }>[] | undefined; } & { instructionSets: Readonly<{ title?: string | undefined; callOut?: Readonly<{ message?: string | undefined; iconType?: string | undefined; } & { title: string; }> | undefined; statusCheck?: Readonly<{ error?: string | undefined; success?: string | undefined; text?: string | undefined; title?: string | undefined; btnLabel?: string | undefined; } & { esHitsCheck: Readonly<{} & { query: Record; index: string | string[]; }>; }> | undefined; } & { instructionVariants: Readonly<{ initialSelected?: boolean | undefined; } & { id: string; instructions: Readonly<{ title?: string | undefined; commands?: string[] | undefined; textPre?: string | undefined; textPost?: string | undefined; customComponentName?: string | undefined; } & {}>[]; }>[]; }>[]; }>; }>" ], "path": "src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts", "deprecated": false, @@ -1868,7 +1868,7 @@ "label": "TutorialSchema", "description": [], "signature": [ - "{ readonly isBeta?: boolean | undefined; readonly savedObjects?: any[] | undefined; readonly euiIconType?: string | undefined; readonly previewImagePath?: string | undefined; readonly moduleName?: string | undefined; readonly completionTimeMinutes?: number | undefined; readonly elasticCloud?: Readonly<{ params?: Readonly<{ defaultValue?: any; } & { type: \"string\" | \"number\"; id: string; label: string; }>[] | undefined; } & { instructionSets: Readonly<{ title?: string | undefined; callOut?: Readonly<{ message?: string | undefined; iconType?: string | undefined; } & { title: string; }> | undefined; statusCheck?: Readonly<{ error?: string | undefined; success?: string | undefined; text?: string | undefined; title?: string | undefined; btnLabel?: string | undefined; } & { esHitsCheck: Readonly<{} & { query: Record; index: string | string[]; }>; }> | undefined; } & { instructionVariants: Readonly<{ initialSelected?: boolean | undefined; } & { id: string; instructions: Readonly<{ title?: string | undefined; commands?: string[] | undefined; textPre?: string | undefined; textPost?: string | undefined; customComponentName?: string | undefined; } & {}>[]; }>[]; }>[]; }> | undefined; readonly onPremElasticCloud?: Readonly<{ params?: Readonly<{ defaultValue?: any; } & { type: \"string\" | \"number\"; id: string; label: string; }>[] | undefined; } & { instructionSets: Readonly<{ title?: string | undefined; callOut?: Readonly<{ message?: string | undefined; iconType?: string | undefined; } & { title: string; }> | undefined; statusCheck?: Readonly<{ error?: string | undefined; success?: string | undefined; text?: string | undefined; title?: string | undefined; btnLabel?: string | undefined; } & { esHitsCheck: Readonly<{} & { query: Record; index: string | string[]; }>; }> | undefined; } & { instructionVariants: Readonly<{ initialSelected?: boolean | undefined; } & { id: string; instructions: Readonly<{ title?: string | undefined; commands?: string[] | undefined; textPre?: string | undefined; textPost?: string | undefined; customComponentName?: string | undefined; } & {}>[]; }>[]; }>[]; }> | undefined; readonly artifacts?: Readonly<{ application?: Readonly<{} & { path: string; label: string; }> | undefined; exportedFields?: Readonly<{} & { documentationUrl: string; }> | undefined; } & { dashboards: Readonly<{ linkLabel?: string | undefined; } & { id: string; isOverview: boolean; }>[]; }> | undefined; readonly savedObjectsInstallMsg?: string | undefined; readonly customStatusCheckName?: string | undefined; readonly integrationBrowserCategories?: string[] | undefined; readonly eprPackageOverlap?: string | undefined; readonly id: string; readonly name: string; readonly category: \"other\" | \"security\" | \"metrics\" | \"logging\"; readonly shortDescription: string; readonly longDescription: string; readonly onPrem: Readonly<{ params?: Readonly<{ defaultValue?: any; } & { type: \"string\" | \"number\"; id: string; label: string; }>[] | undefined; } & { instructionSets: Readonly<{ title?: string | undefined; callOut?: Readonly<{ message?: string | undefined; iconType?: string | undefined; } & { title: string; }> | undefined; statusCheck?: Readonly<{ error?: string | undefined; success?: string | undefined; text?: string | undefined; title?: string | undefined; btnLabel?: string | undefined; } & { esHitsCheck: Readonly<{} & { query: Record; index: string | string[]; }>; }> | undefined; } & { instructionVariants: Readonly<{ initialSelected?: boolean | undefined; } & { id: string; instructions: Readonly<{ title?: string | undefined; commands?: string[] | undefined; textPre?: string | undefined; textPost?: string | undefined; customComponentName?: string | undefined; } & {}>[]; }>[]; }>[]; }>; }" + "{ readonly isBeta?: boolean | undefined; readonly savedObjects?: any[] | undefined; readonly euiIconType?: string | undefined; readonly previewImagePath?: string | undefined; readonly moduleName?: string | undefined; readonly completionTimeMinutes?: number | undefined; readonly elasticCloud?: Readonly<{ params?: Readonly<{ defaultValue?: any; } & { type: \"string\" | \"number\"; id: string; label: string; }>[] | undefined; } & { instructionSets: Readonly<{ title?: string | undefined; callOut?: Readonly<{ message?: string | undefined; iconType?: string | undefined; } & { title: string; }> | undefined; statusCheck?: Readonly<{ error?: string | undefined; success?: string | undefined; text?: string | undefined; title?: string | undefined; btnLabel?: string | undefined; } & { esHitsCheck: Readonly<{} & { query: Record; index: string | string[]; }>; }> | undefined; } & { instructionVariants: Readonly<{ initialSelected?: boolean | undefined; } & { id: string; instructions: Readonly<{ title?: string | undefined; commands?: string[] | undefined; textPre?: string | undefined; textPost?: string | undefined; customComponentName?: string | undefined; } & {}>[]; }>[]; }>[]; }> | undefined; readonly onPremElasticCloud?: Readonly<{ params?: Readonly<{ defaultValue?: any; } & { type: \"string\" | \"number\"; id: string; label: string; }>[] | undefined; } & { instructionSets: Readonly<{ title?: string | undefined; callOut?: Readonly<{ message?: string | undefined; iconType?: string | undefined; } & { title: string; }> | undefined; statusCheck?: Readonly<{ error?: string | undefined; success?: string | undefined; text?: string | undefined; title?: string | undefined; btnLabel?: string | undefined; } & { esHitsCheck: Readonly<{} & { query: Record; index: string | string[]; }>; }> | undefined; } & { instructionVariants: Readonly<{ initialSelected?: boolean | undefined; } & { id: string; instructions: Readonly<{ title?: string | undefined; commands?: string[] | undefined; textPre?: string | undefined; textPost?: string | undefined; customComponentName?: string | undefined; } & {}>[]; }>[]; }>[]; }> | undefined; readonly artifacts?: Readonly<{ application?: Readonly<{} & { path: string; label: string; }> | undefined; exportedFields?: Readonly<{} & { documentationUrl: string; }> | undefined; } & { dashboards: Readonly<{ linkLabel?: string | undefined; } & { id: string; isOverview: boolean; }>[]; }> | undefined; readonly savedObjectsInstallMsg?: string | undefined; readonly customStatusCheckName?: string | undefined; readonly integrationBrowserCategories?: string[] | undefined; readonly eprPackageOverlap?: string | undefined; readonly name: string; readonly category: \"other\" | \"security\" | \"metrics\" | \"logging\"; readonly id: string; readonly shortDescription: string; readonly longDescription: string; readonly onPrem: Readonly<{ params?: Readonly<{ defaultValue?: any; } & { type: \"string\" | \"number\"; id: string; label: string; }>[] | undefined; } & { instructionSets: Readonly<{ title?: string | undefined; callOut?: Readonly<{ message?: string | undefined; iconType?: string | undefined; } & { title: string; }> | undefined; statusCheck?: Readonly<{ error?: string | undefined; success?: string | undefined; text?: string | undefined; title?: string | undefined; btnLabel?: string | undefined; } & { esHitsCheck: Readonly<{} & { query: Record; index: string | string[]; }>; }> | undefined; } & { instructionVariants: Readonly<{ initialSelected?: boolean | undefined; } & { id: string; instructions: Readonly<{ title?: string | undefined; commands?: string[] | undefined; textPre?: string | undefined; textPost?: string | undefined; customComponentName?: string | undefined; } & {}>[]; }>[]; }>[]; }>; }" ], "path": "src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts", "deprecated": false, @@ -2160,7 +2160,7 @@ "signature": [ "{ getSampleDatasets: () => ", "Writable", - "[]; previewImagePath: string; overviewDashboard: string; defaultIndex: string; dataIndices: Readonly<{} & { id: string; fields: Record; timeFields: string[]; dataPath: string; currentTimeMarker: string; preserveDayOfWeekTimeOfDay: boolean; }>[]; }>>[]; addSavedObjectsToSampleDataset: (id: string, savedObjects: ", + "[]; previewImagePath: string; overviewDashboard: string; defaultIndex: string; dataIndices: Readonly<{} & { id: string; fields: Record; timeFields: string[]; dataPath: string; currentTimeMarker: string; preserveDayOfWeekTimeOfDay: boolean; }>[]; }>>[]; addSavedObjectsToSampleDataset: (id: string, savedObjects: ", "SavedObject", "[]) => void; addAppLinksToSampleDataset: (id: string, appLinks: ", { diff --git a/api_docs/home.mdx b/api_docs/home.mdx index da3d0a809920e..7dac1a970a92e 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index fb081f2f46610..f8eefe8bf4ad6 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: 2022-09-15 +date: 2022-09-19 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 6f831cf4ccac3..53c74db215e23 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: 2022-09-15 +date: 2022-09-19 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 ec8805874ff5c..3a0f894214867 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: 2022-09-15 +date: 2022-09-19 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 fceaefc5787f8..f2b4a61485205 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: 2022-09-15 +date: 2022-09-19 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 13d90c9667487..599700b0a3f5d 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: 2022-09-15 +date: 2022-09-19 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 2f16fee32984b..cf9015e67273d 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: 2022-09-15 +date: 2022-09-19 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 3ff7bbd3bbb8e..f0771d4b5f804 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.devdocs.json b/api_docs/kbn_aiops_utils.devdocs.json index e2ece6ca9c92c..5fae256126a14 100644 --- a/api_docs/kbn_aiops_utils.devdocs.json +++ b/api_docs/kbn_aiops_utils.devdocs.json @@ -190,7 +190,7 @@ "Headers", ", logger: ", "Logger", - ") => StreamFactoryReturnType" + ", flushFix: boolean | undefined) => StreamFactoryReturnType" ], "path": "x-pack/packages/ml/aiops_utils/src/stream_factory.ts", "deprecated": false, @@ -227,6 +227,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "@kbn/aiops-utils", + "id": "def-common.streamFactory.$3", + "type": "CompoundType", + "tags": [], + "label": "flushFix", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/packages/ml/aiops_utils/src/stream_factory.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [ @@ -248,7 +263,7 @@ "Headers", ", logger: ", "Logger", - ") => StreamFactoryReturnType" + ", flushFix: boolean) => StreamFactoryReturnType" ], "path": "x-pack/packages/ml/aiops_utils/src/stream_factory.ts", "deprecated": false, @@ -285,6 +300,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "@kbn/aiops-utils", + "id": "def-common.streamFactory.$3", + "type": "boolean", + "tags": [], + "label": "flushFix", + "description": [], + "signature": [ + "boolean" + ], + "path": "x-pack/packages/ml/aiops_utils/src/stream_factory.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [ diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 40f752ece7346..7b49f322afd2c 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact Machine Learning UI for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 49 | 0 | 24 | 0 | +| 51 | 0 | 26 | 0 | ## Common diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index e355f16ce354a..1c1d84479c938 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: 2022-09-15 +date: 2022-09-19 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 575ed95268a37..b7d1fc106508f 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: 2022-09-15 +date: 2022-09-19 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 07d94ea78c46e..777c7aad48330 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: 2022-09-15 +date: 2022-09-19 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 8fc1987aeb2d3..c31befc1b7cb0 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: 2022-09-15 +date: 2022-09-19 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 9bdc6d59a32b5..a2b6256f28d04 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: 2022-09-15 +date: 2022-09-19 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 2b2ddc6e6fc01..36826f52d6437 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: 2022-09-15 +date: 2022-09-19 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 0ab59c7550240..a040b38ad1ed9 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 053ab6a88c153..37dce3b4f3368 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_apm_synthtrace.devdocs.json index 3ac0d3855ecb1..565fde2f17163 100644 --- a/api_docs/kbn_apm_synthtrace.devdocs.json +++ b/api_docs/kbn_apm_synthtrace.devdocs.json @@ -1024,7 +1024,7 @@ }, "[]; 'error.grouping_name': string; 'error.grouping_key': string; 'host.name': string; 'host.hostname': string; 'kubernetes.pod.uid': string; 'kubernetes.pod.name': string; 'metricset.name': string; observer: ", "Observer", - "; 'parent.id': string; 'processor.event': string; 'processor.name': string; 'trace.id': string; 'transaction.name': string; 'transaction.type': string; 'transaction.id': string; 'transaction.duration.us': number; 'transaction.duration.histogram': { values: number[]; counts: number[]; }; 'transaction.sampled': true; 'service.name': string; 'service.version': string; 'service.environment': string; 'service.node.name': string; 'service.runtime.name': string; 'service.runtime.version': string; 'service.framework.name': string; 'service.target.name': string; 'service.target.type': string; 'span.id': string; 'span.name': string; 'span.type': string; 'span.subtype': string; 'span.duration.us': number; 'span.destination.service.name': string; 'span.destination.service.resource': string; 'span.destination.service.type': string; 'span.destination.service.response_time.sum.us': number; 'span.destination.service.response_time.count': number; 'span.self_time.count': number; 'span.self_time.sum.us': number; 'span.links': { trace: { id: string; }; span: { id: string; }; }[]; 'cloud.provider': string; 'cloud.project.name': string; 'cloud.service.name': string; 'cloud.availability_zone': string; 'cloud.machine.type': string; 'cloud.region': string; 'host.os.platform': string; 'faas.id': string; 'faas.coldstart': boolean; 'faas.execution': string; 'faas.trigger.type': string; 'faas.trigger.request_id': 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; }>" + "; 'parent.id': string; 'processor.event': string; 'processor.name': string; 'trace.id': string; 'transaction.name': string; 'transaction.type': string; 'transaction.id': string; 'transaction.duration.us': number; 'transaction.duration.histogram': { values: number[]; counts: number[]; }; 'transaction.sampled': true; 'service.name': string; 'service.version': string; 'service.environment': string; 'service.node.name': string; 'service.runtime.name': string; 'service.runtime.version': string; 'service.framework.name': string; 'service.target.name': string; 'service.target.type': string; 'span.id': string; 'span.name': string; 'span.type': string; 'span.subtype': string; 'span.duration.us': number; 'span.destination.service.name': string; 'span.destination.service.resource': string; 'span.destination.service.type': string; 'span.destination.service.response_time.sum.us': number; 'span.destination.service.response_time.count': number; 'span.self_time.count': number; 'span.self_time.sum.us': number; 'span.links': { trace: { id: string; }; span: { id: string; }; }[]; 'cloud.provider': string; 'cloud.project.name': string; 'cloud.service.name': string; 'cloud.availability_zone': string; 'cloud.machine.type': string; 'cloud.region': string; 'host.os.platform': string; 'faas.id': string; 'faas.name': string; 'faas.coldstart': boolean; 'faas.execution': string; 'faas.trigger.type': string; 'faas.trigger.request_id': 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; }>" ], "path": "packages/kbn-apm-synthtrace/src/lib/apm/apm_fields.ts", "deprecated": false, @@ -1134,7 +1134,7 @@ }, "[] | undefined; 'error.grouping_name'?: string | undefined; 'error.grouping_key'?: string | undefined; 'host.name'?: string | undefined; 'host.hostname'?: string | undefined; 'kubernetes.pod.uid'?: string | undefined; 'kubernetes.pod.name'?: string | undefined; observer?: ", "Observer", - " | undefined; 'parent.id'?: string | undefined; 'processor.event'?: string | undefined; 'processor.name'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.name'?: string | undefined; 'transaction.type'?: string | undefined; 'transaction.id'?: string | undefined; 'transaction.duration.us'?: number | undefined; 'transaction.sampled'?: true | undefined; 'service.name'?: string | undefined; 'service.version'?: string | undefined; 'service.environment'?: string | undefined; 'service.node.name'?: string | undefined; 'service.runtime.name'?: string | undefined; 'service.runtime.version'?: string | undefined; 'service.framework.name'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.type'?: string | undefined; 'span.id'?: string | undefined; 'span.name'?: string | undefined; 'span.type'?: string | undefined; 'span.subtype'?: string | undefined; 'span.duration.us'?: number | undefined; 'span.destination.service.name'?: string | undefined; 'span.destination.service.resource'?: string | undefined; 'span.destination.service.type'?: string | undefined; 'span.destination.service.response_time.sum.us'?: number | undefined; 'span.destination.service.response_time.count'?: number | undefined; 'span.self_time.count'?: number | undefined; 'span.self_time.sum.us'?: number | undefined; 'span.links'?: { trace: { id: string; }; span: { id: string; }; }[] | undefined; 'cloud.provider'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.region'?: string | undefined; 'host.os.platform'?: string | undefined; 'faas.id'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.trigger.type'?: string | undefined; 'faas.trigger.request_id'?: string | undefined; 'system.process.memory.size'?: number | undefined; 'system.memory.actual.free'?: number | undefined; 'system.memory.total'?: number | undefined; 'system.cpu.total.norm.pct'?: number | undefined; 'system.process.memory.rss.bytes'?: number | undefined; 'system.process.cpu.total.norm.pct'?: number | undefined; 'jvm.memory.heap.used'?: number | undefined; 'jvm.memory.non_heap.used'?: number | undefined; 'jvm.thread.count'?: number | undefined; }[]" + " | undefined; 'parent.id'?: string | undefined; 'processor.event'?: string | undefined; 'processor.name'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.name'?: string | undefined; 'transaction.type'?: string | undefined; 'transaction.id'?: string | undefined; 'transaction.duration.us'?: number | undefined; 'transaction.sampled'?: true | undefined; 'service.name'?: string | undefined; 'service.version'?: string | undefined; 'service.environment'?: string | undefined; 'service.node.name'?: string | undefined; 'service.runtime.name'?: string | undefined; 'service.runtime.version'?: string | undefined; 'service.framework.name'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.type'?: string | undefined; 'span.id'?: string | undefined; 'span.name'?: string | undefined; 'span.type'?: string | undefined; 'span.subtype'?: string | undefined; 'span.duration.us'?: number | undefined; 'span.destination.service.name'?: string | undefined; 'span.destination.service.resource'?: string | undefined; 'span.destination.service.type'?: string | undefined; 'span.destination.service.response_time.sum.us'?: number | undefined; 'span.destination.service.response_time.count'?: number | undefined; 'span.self_time.count'?: number | undefined; 'span.self_time.sum.us'?: number | undefined; 'span.links'?: { trace: { id: string; }; span: { id: string; }; }[] | undefined; 'cloud.provider'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.region'?: string | undefined; 'host.os.platform'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.trigger.type'?: string | undefined; 'faas.trigger.request_id'?: string | undefined; 'system.process.memory.size'?: number | undefined; 'system.memory.actual.free'?: number | undefined; 'system.memory.total'?: number | undefined; 'system.cpu.total.norm.pct'?: number | undefined; 'system.process.memory.rss.bytes'?: number | undefined; 'system.process.cpu.total.norm.pct'?: number | undefined; 'jvm.memory.heap.used'?: number | undefined; 'jvm.memory.non_heap.used'?: number | undefined; 'jvm.thread.count'?: number | undefined; 'faas.billed_duration'?: number | undefined; 'faas.timeout'?: number | undefined; 'faas.coldstart_duration'?: number | undefined; 'faas.duration'?: number | undefined; }[]" ], "path": "packages/kbn-apm-synthtrace/src/lib/apm/index.ts", "deprecated": false, @@ -1190,7 +1190,7 @@ }, "[] | undefined; 'error.grouping_name'?: string | undefined; 'error.grouping_key'?: string | undefined; 'host.name'?: string | undefined; 'host.hostname'?: string | undefined; 'kubernetes.pod.uid'?: string | undefined; 'kubernetes.pod.name'?: string | undefined; observer?: ", "Observer", - " | undefined; 'parent.id'?: string | undefined; 'processor.event'?: string | undefined; 'processor.name'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.name'?: string | undefined; 'transaction.type'?: string | undefined; 'transaction.id'?: string | undefined; 'transaction.duration.us'?: number | undefined; 'transaction.duration.histogram'?: { values: number[]; counts: number[]; } | undefined; 'transaction.sampled'?: true | undefined; 'service.name'?: string | undefined; 'service.version'?: string | undefined; 'service.environment'?: string | undefined; 'service.node.name'?: string | undefined; 'service.runtime.name'?: string | undefined; 'service.runtime.version'?: string | undefined; 'service.framework.name'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.type'?: string | undefined; 'span.id'?: string | undefined; 'span.name'?: string | undefined; 'span.type'?: string | undefined; 'span.subtype'?: string | undefined; 'span.duration.us'?: number | undefined; 'span.destination.service.name'?: string | undefined; 'span.destination.service.resource'?: string | undefined; 'span.destination.service.type'?: string | undefined; 'span.self_time.count'?: number | undefined; 'span.self_time.sum.us'?: number | undefined; 'span.links'?: { trace: { id: string; }; span: { id: string; }; }[] | undefined; 'cloud.provider'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.region'?: string | undefined; 'host.os.platform'?: string | undefined; 'faas.id'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.trigger.type'?: string | undefined; 'faas.trigger.request_id'?: string | undefined; 'system.process.memory.size'?: number | undefined; 'system.memory.actual.free'?: number | undefined; 'system.memory.total'?: number | undefined; 'system.cpu.total.norm.pct'?: number | undefined; 'system.process.memory.rss.bytes'?: number | undefined; 'system.process.cpu.total.norm.pct'?: number | undefined; 'jvm.memory.heap.used'?: number | undefined; 'jvm.memory.non_heap.used'?: number | undefined; 'jvm.thread.count'?: number | undefined; }[]" + " | undefined; 'parent.id'?: string | undefined; 'processor.event'?: string | undefined; 'processor.name'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.name'?: string | undefined; 'transaction.type'?: string | undefined; 'transaction.id'?: string | undefined; 'transaction.duration.us'?: number | undefined; 'transaction.duration.histogram'?: { values: number[]; counts: number[]; } | undefined; 'transaction.sampled'?: true | undefined; 'service.name'?: string | undefined; 'service.version'?: string | undefined; 'service.environment'?: string | undefined; 'service.node.name'?: string | undefined; 'service.runtime.name'?: string | undefined; 'service.runtime.version'?: string | undefined; 'service.framework.name'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.type'?: string | undefined; 'span.id'?: string | undefined; 'span.name'?: string | undefined; 'span.type'?: string | undefined; 'span.subtype'?: string | undefined; 'span.duration.us'?: number | undefined; 'span.destination.service.name'?: string | undefined; 'span.destination.service.resource'?: string | undefined; 'span.destination.service.type'?: string | undefined; 'span.self_time.count'?: number | undefined; 'span.self_time.sum.us'?: number | undefined; 'span.links'?: { trace: { id: string; }; span: { id: string; }; }[] | undefined; 'cloud.provider'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.region'?: string | undefined; 'host.os.platform'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.trigger.type'?: string | undefined; 'faas.trigger.request_id'?: string | undefined; 'system.process.memory.size'?: number | undefined; 'system.memory.actual.free'?: number | undefined; 'system.memory.total'?: number | undefined; 'system.cpu.total.norm.pct'?: number | undefined; 'system.process.memory.rss.bytes'?: number | undefined; 'system.process.cpu.total.norm.pct'?: number | undefined; 'jvm.memory.heap.used'?: number | undefined; 'jvm.memory.non_heap.used'?: number | undefined; 'jvm.thread.count'?: number | undefined; 'faas.billed_duration'?: number | undefined; 'faas.timeout'?: number | undefined; 'faas.coldstart_duration'?: number | undefined; 'faas.duration'?: number | undefined; }[]" ], "path": "packages/kbn-apm-synthtrace/src/lib/apm/index.ts", "deprecated": false, @@ -1362,6 +1362,38 @@ "path": "packages/kbn-apm-synthtrace/src/lib/apm/index.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-synthtrace", + "id": "def-server.apm.serverlessFunction", + "type": "Function", + "tags": [], + "label": "serverlessFunction", + "description": [], + "signature": [ + "({ functionName, serviceName, environment, agentName, }: { functionName: string; environment: string; agentName: string; serviceName?: string | undefined; }) => ", + "ServerlessFunction" + ], + "path": "packages/kbn-apm-synthtrace/src/lib/apm/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/apm-synthtrace", + "id": "def-server.apm.serverlessFunction.$1", + "type": "Object", + "tags": [], + "label": "__0", + "description": [], + "signature": [ + "{ functionName: string; environment: string; agentName: string; serviceName?: string | undefined; }" + ], + "path": "packages/kbn-apm-synthtrace/src/lib/apm/serverless_function.ts", + "deprecated": false, + "trackAdoption": false + } + ] } ], "initialIsOpen": false diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 08c5aa0f155f6..51a1ee17b9115 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 72 | 0 | 72 | 12 | +| 74 | 0 | 74 | 13 | ## Server diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 88cfbb334ac6e..05a6955122d97 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: 2022-09-15 +date: 2022-09-19 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 a6b3056eee178..1b087de6ae21f 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index c399c69b6747f..7061986c8f12c 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: 2022-09-15 +date: 2022-09-19 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 cbcf6bb624273..a38b2f8b7463d 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: 2022-09-15 +date: 2022-09-19 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 97a3815f1ffbb..0fc81d89278aa 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: 2022-09-15 +date: 2022-09-19 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 065f925683b2e..1c64d868f468a 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: 2022-09-15 +date: 2022-09-19 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 a722251d51569..162874ebcc382 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_coloring.devdocs.json b/api_docs/kbn_coloring.devdocs.json index f8c78bf06c6b9..8c6054e41c806 100644 --- a/api_docs/kbn_coloring.devdocs.json +++ b/api_docs/kbn_coloring.devdocs.json @@ -1860,7 +1860,7 @@ "label": "RequiredPaletteParamTypes", "description": [], "signature": [ - "{ rangeMin: number; name: string; reverse: boolean; rangeType: \"number\" | \"percent\"; continuity: ", + "{ name: string; rangeMin: number; reverse: boolean; rangeType: \"number\" | \"percent\"; continuity: ", { "pluginId": "@kbn/coloring", "scope": "common", diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index f4c6612cda718..d6d0f22cc201b 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: 2022-09-15 +date: 2022-09-19 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 34ef9bc76c931..7bf78620a8feb 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: 2022-09-15 +date: 2022-09-19 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 af2009c0884f3..735b2940d457f 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: 2022-09-15 +date: 2022-09-19 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 62b738288045e..4411edb9b4b0e 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 0111ef4073953..063a8e5ac81fa 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: 2022-09-15 +date: 2022-09-19 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 7b0d245fec26f..e468b04c8d7e3 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: 2022-09-15 +date: 2022-09-19 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 a4a448c344756..c726d99414732 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: 2022-09-15 +date: 2022-09-19 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 c22547c3a94df..6c2306eeae323 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: 2022-09-15 +date: 2022-09-19 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 442397fe88c22..0382b4968171a 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: 2022-09-15 +date: 2022-09-19 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 51f24030b0398..f782fd23f0b8a 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: 2022-09-15 +date: 2022-09-19 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 b83078ff29391..842f44d2458a8 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: 2022-09-15 +date: 2022-09-19 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 a288df63060bf..eac3abd124a04 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: 2022-09-15 +date: 2022-09-19 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 6c962fb38cd1b..e4bbac7e7ef22 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: 2022-09-15 +date: 2022-09-19 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 ea7fd6b61449a..78172ef70918c 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: 2022-09-15 +date: 2022-09-19 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 e438455ae0698..2fd5f53a8515a 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: 2022-09-15 +date: 2022-09-19 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 ee5cc597e8fd6..2c918292eac64 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: 2022-09-15 +date: 2022-09-19 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_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 8674e74bf9b52..f6e2ec8ac6a86 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: 2022-09-15 +date: 2022-09-19 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 a291f4ea5f7ba..b75a290d38b9b 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: 2022-09-15 +date: 2022-09-19 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 6d04efb623429..f27a81c0bad1f 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: 2022-09-15 +date: 2022-09-19 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 5138c5ab12f74..c48e9edb7d4e1 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: 2022-09-15 +date: 2022-09-19 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 bc0923df3403b..974d0a03d4151 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: 2022-09-15 +date: 2022-09-19 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 75f7bd5e01c08..3e0493173f170 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: 2022-09-15 +date: 2022-09-19 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 7dd592c3cd082..7b840e86b134a 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: 2022-09-15 +date: 2022-09-19 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 a26c460d27b3a..972d5d68ab911 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: 2022-09-15 +date: 2022-09-19 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 fa6d180770fed..e8623f0c05f6f 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: 2022-09-15 +date: 2022-09-19 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 8cbeb740b1795..04e3666adc183 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: 2022-09-15 +date: 2022-09-19 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 620e70ce1ec1c..5315919d0c106 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: 2022-09-15 +date: 2022-09-19 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_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index 24a5f5e298dcb..fc52edec60b65 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: 2022-09-15 +date: 2022-09-19 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 670bef0ded598..3185fc2880e04 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: 2022-09-15 +date: 2022-09-19 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 a62a696dbc86a..803edcdb542d4 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: 2022-09-15 +date: 2022-09-19 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 fe538a3178146..3c66778540176 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: 2022-09-15 +date: 2022-09-19 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 0ce0640387f0d..ccd11240aaf39 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: 2022-09-15 +date: 2022-09-19 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 276dbcd924f51..70ab9a5665bdb 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: 2022-09-15 +date: 2022-09-19 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 fe93c13d76f05..804a7ca075696 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: 2022-09-15 +date: 2022-09-19 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 e7a091ffc7850..1389d27cd3777 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: 2022-09-15 +date: 2022-09-19 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 03fe6cfb39fdf..d34d1176ff24f 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: 2022-09-15 +date: 2022-09-19 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 b44ad58b50fed..b5e85f32608fa 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: 2022-09-15 +date: 2022-09-19 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 8d13ec60e6d1c..00ac807b1a24c 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: 2022-09-15 +date: 2022-09-19 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 c768a8edeffb0..397da70c91a47 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_core_elasticsearch_client_server_mocks.devdocs.json index a9b967b7e2c98..c682403d3d63d 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.devdocs.json +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.devdocs.json @@ -237,7 +237,7 @@ "label": "asInternalUser", "description": [], "signature": [ - "{ get: ", + "{ name: string | symbol; get: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", "scope": "server", @@ -341,7 +341,7 @@ }, "<", "default", - ">; name: string | symbol; index: ", + ">; index: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", "scope": "server", @@ -1348,7 +1348,7 @@ "label": "asInternalUser", "description": [], "signature": [ - "{ get: ", + "{ name: string | symbol; get: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", "scope": "server", @@ -1452,7 +1452,7 @@ }, "<", "default", - ">; name: string | symbol; index: ", + ">; index: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", "scope": "server", @@ -2413,7 +2413,7 @@ "label": "asCurrentUser", "description": [], "signature": [ - "{ get: ", + "{ name: string | symbol; get: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", "scope": "server", @@ -2517,7 +2517,7 @@ }, "<", "default", - ">; name: string | symbol; index: ", + ">; index: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", "scope": "server", @@ -3545,7 +3545,7 @@ "label": "ElasticsearchClientMock", "description": [], "signature": [ - "{ get: ", + "{ name: string | symbol; get: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", "scope": "server", @@ -3649,7 +3649,7 @@ }, "<", "default", - ">; name: string | symbol; index: ", + ">; index: ", { "pluginId": "@kbn/core-elasticsearch-client-server-mocks", "scope": "server", diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index d61712c6c3228..f41398f5dea8e 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_core_elasticsearch_server.devdocs.json index 7f41607270e7a..a6bbfb7a58cb9 100644 --- a/api_docs/kbn_core_elasticsearch_server.devdocs.json +++ b/api_docs/kbn_core_elasticsearch_server.devdocs.json @@ -883,7 +883,7 @@ "\nA {@link ElasticsearchClient | client} to be used to query the ES cluster on behalf of the Kibana internal user" ], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -1001,7 +1001,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", @@ -2499,7 +2499,7 @@ "\nA {@link ElasticsearchClient | client} to be used to query the elasticsearch cluster\non behalf of the internal Kibana user." ], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -2617,7 +2617,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", @@ -3703,7 +3703,7 @@ "\nA {@link ElasticsearchClient | client} to be used to query the elasticsearch cluster\non behalf of the user that initiated the request to the Kibana server." ], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -3821,7 +3821,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", @@ -5154,7 +5154,7 @@ "\nClient used to query the elasticsearch cluster.\n" ], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -5272,7 +5272,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 293ac767bc8cf..93cd2d6d22ee4 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_core_elasticsearch_server_internal.devdocs.json index a040b2f6fb20c..248c3b356fa14 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.devdocs.json +++ b/api_docs/kbn_core_elasticsearch_server_internal.devdocs.json @@ -46,7 +46,7 @@ "label": "client", "description": [], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -164,7 +164,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", @@ -1629,7 +1629,7 @@ "label": "internalClient", "description": [], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -1747,7 +1747,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index b7e974cdd2074..f4ca963c49813 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: 2022-09-15 +date: 2022-09-19 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 ed9daf2559199..6ff323a22e364 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: 2022-09-15 +date: 2022-09-19 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 4810123b9488c..29255cf8c549a 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: 2022-09-15 +date: 2022-09-19 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 6481368cf41e6..cce830ea74a44 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: 2022-09-15 +date: 2022-09-19 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 f2cd585c5a2ac..76bf550dc69c3 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: 2022-09-15 +date: 2022-09-19 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 010fdbd6dd5ea..26a93ff03f157 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: 2022-09-15 +date: 2022-09-19 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 bf58c2c728369..87dff478e355a 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: 2022-09-15 +date: 2022-09-19 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 1687962ce4571..9cb56f4ca1d7c 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: 2022-09-15 +date: 2022-09-19 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 f1f8b736ecd7b..e3e28e067bd1a 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: 2022-09-15 +date: 2022-09-19 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 b52a9efea2a96..15afc420560ee 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: 2022-09-15 +date: 2022-09-19 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 269e1a7227906..14e79479e149a 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: 2022-09-15 +date: 2022-09-19 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 20b70c5b42cf7..6fcb37a18bf2e 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: 2022-09-15 +date: 2022-09-19 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 a02a546d819b1..b7a113051c2ca 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: 2022-09-15 +date: 2022-09-19 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 9f8966957e8d3..75a604a03dbe1 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: 2022-09-15 +date: 2022-09-19 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 a635bb3961460..003bc1ffb00f7 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: 2022-09-15 +date: 2022-09-19 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 e1eb359487726..c52b818c8c93d 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: 2022-09-15 +date: 2022-09-19 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 bee6f8f992b13..d21e9cea15181 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: 2022-09-15 +date: 2022-09-19 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 d098e3cf23caf..b0fb30c02ff62 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: 2022-09-15 +date: 2022-09-19 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_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index c0f9b99007a21..c8827b75457b4 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: 2022-09-15 +date: 2022-09-19 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 46e846225490d..8d8598e43e97d 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_core_http_server.devdocs.json index 6fbfbacd7757a..2fdd0ff9d2a33 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -4544,7 +4544,7 @@ "label": "options", "description": [], "signature": [ - "Method extends \"get\" | \"options\" ? Required; readonly ssl: Readonly<{ key?: string | undefined; certificateAuthorities?: string | string[] | undefined; certificate?: string | undefined; keyPassphrase?: string | undefined; redirectHttpFromPort?: number | undefined; } & { enabled: boolean; keystore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; truststore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; cipherSuites: string[]; supportedProtocols: string[]; clientAuthentication: \"optional\" | \"none\" | \"required\"; }>; readonly port: number; readonly cors: Readonly<{} & { enabled: boolean; allowCredentials: boolean; allowOrigin: string[] | \"*\"[]; }>; readonly autoListen: boolean; readonly shutdownTimeout: moment.Duration; readonly securityResponseHeaders: Readonly<{} & { referrerPolicy: \"origin\" | \"no-referrer\" | \"no-referrer-when-downgrade\" | \"origin-when-cross-origin\" | \"same-origin\" | \"strict-origin\" | \"strict-origin-when-cross-origin\" | \"unsafe-url\" | null; disableEmbedding: boolean; strictTransportSecurity: string | null; xContentTypeOptions: \"nosniff\" | null; permissionsPolicy: string | null; }>; readonly customResponseHeaders: Record; readonly maxPayload: ", + "{ readonly basePath?: string | undefined; readonly uuid?: string | undefined; readonly publicBaseUrl?: string | undefined; readonly name: string; readonly host: string; readonly compression: Readonly<{ referrerWhitelist?: string[] | undefined; } & { enabled: boolean; }>; readonly ssl: Readonly<{ key?: string | undefined; certificateAuthorities?: string | string[] | undefined; certificate?: string | undefined; keyPassphrase?: string | undefined; redirectHttpFromPort?: number | undefined; } & { enabled: boolean; keystore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; truststore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; cipherSuites: string[]; supportedProtocols: string[]; clientAuthentication: \"optional\" | \"none\" | \"required\"; }>; readonly port: number; readonly cors: Readonly<{} & { enabled: boolean; allowCredentials: boolean; allowOrigin: string[] | \"*\"[]; }>; readonly autoListen: boolean; readonly shutdownTimeout: moment.Duration; readonly securityResponseHeaders: Readonly<{} & { referrerPolicy: \"origin\" | \"no-referrer\" | \"no-referrer-when-downgrade\" | \"origin-when-cross-origin\" | \"same-origin\" | \"strict-origin\" | \"strict-origin-when-cross-origin\" | \"unsafe-url\" | null; disableEmbedding: boolean; strictTransportSecurity: string | null; xContentTypeOptions: \"nosniff\" | null; permissionsPolicy: string | null; }>; readonly customResponseHeaders: Record; readonly maxPayload: ", "ByteSizeValue", "; readonly rewriteBasePath: boolean; readonly keepaliveTimeout: number; readonly socketTimeout: number; readonly xsrf: Readonly<{} & { disableProtection: boolean; allowlist: string[]; }>; readonly requestId: Readonly<{} & { allowFromAnyIp: boolean; ipAllowlist: string[]; }>; }" ], diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index fe55635d6b969..3b1f1a5751710 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: 2022-09-15 +date: 2022-09-19 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 7c41a909db7c9..377d760c606d0 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: 2022-09-15 +date: 2022-09-19 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 c258392ad24fa..914a823003e3d 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: 2022-09-15 +date: 2022-09-19 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 3e6e23f60fad5..2d8ec30386de6 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: 2022-09-15 +date: 2022-09-19 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 4e2f7741698f1..3662bfa9649bf 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: 2022-09-15 +date: 2022-09-19 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 9c47ade4d4523..d428a0dd8cbb9 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: 2022-09-15 +date: 2022-09-19 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 24d93abaa4df5..190adb30ef63d 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: 2022-09-15 +date: 2022-09-19 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.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index 1d2f12bb38631..7ae978f141a96 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] --- import kbnCoreInjectedMetadataBrowserObj from './kbn_core_injected_metadata_browser.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 a5c6f62ac8254..4d0cac6b89de8 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: 2022-09-15 +date: 2022-09-19 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 48010f098dd8e..f5c8643810032 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: 2022-09-15 +date: 2022-09-19 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 f82e5aa1d34cf..f024e58377172 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: 2022-09-15 +date: 2022-09-19 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_logging_server.devdocs.json b/api_docs/kbn_core_logging_server.devdocs.json index 0ee04816b3df4..8a5748a6993ce 100644 --- a/api_docs/kbn_core_logging_server.devdocs.json +++ b/api_docs/kbn_core_logging_server.devdocs.json @@ -215,7 +215,7 @@ "label": "level", "description": [], "signature": [ - "\"error\" | \"all\" | \"info\" | \"debug\" | \"off\" | \"trace\" | \"warn\" | \"fatal\"" + "\"error\" | \"all\" | \"info\" | \"debug\" | \"off\" | \"warn\" | \"trace\" | \"fatal\"" ], "path": "packages/core/logging/core-logging-server/src/logger.ts", "deprecated": false, diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 44ccf3ee4c071..94f5e4a359d1f 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_core_logging_server_internal.devdocs.json index d323e46daf7f2..97a1788e84dcb 100644 --- a/api_docs/kbn_core_logging_server_internal.devdocs.json +++ b/api_docs/kbn_core_logging_server_internal.devdocs.json @@ -149,7 +149,7 @@ "AppenderConfigType", ">>; loggers: ", "Type", - "[]>; }>" + "[]>; }>" ], "path": "packages/core/logging/core-logging-server-internal/src/logging_config.ts", "deprecated": false, @@ -173,7 +173,7 @@ "Type", "; level: ", "Type", - "<\"error\" | \"all\" | \"info\" | \"debug\" | \"off\" | \"trace\" | \"warn\" | \"fatal\">; }>" + "<\"error\" | \"all\" | \"info\" | \"debug\" | \"off\" | \"warn\" | \"trace\" | \"fatal\">; }>" ], "path": "packages/core/logging/core-logging-server-internal/src/logging_config.ts", "deprecated": false, diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index 442f4408edb57..55f1e66556208 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: 2022-09-15 +date: 2022-09-19 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 93f111dbdf6fb..3e6512d89ac82 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: 2022-09-15 +date: 2022-09-19 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 9461e0298e913..d4a237f0fa7bc 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: 2022-09-15 +date: 2022-09-19 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 f574ed7a6700c..8fc40fd71a703 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: 2022-09-15 +date: 2022-09-19 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 a36732ece3b66..83ffc4237e8e8 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: 2022-09-15 +date: 2022-09-19 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 52ad923022f0a..6774e9f22ae89 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: 2022-09-15 +date: 2022-09-19 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 701638fd9a30d..c2b10997cb445 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: 2022-09-15 +date: 2022-09-19 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 600c8a59b4d20..8adb96d6d45b0 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: 2022-09-15 +date: 2022-09-19 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 27de36cafb0a1..66c671d1b05d6 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: 2022-09-15 +date: 2022-09-19 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 dc326134fa2c0..2c82e4ab5ea7a 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: 2022-09-15 +date: 2022-09-19 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 7c2c592d69969..a268a5b99f785 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: 2022-09-15 +date: 2022-09-19 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 0047db7651be5..dcf1f7a1eebdb 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: 2022-09-15 +date: 2022-09-19 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 3370a4cecfb86..465ddca705a4d 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: 2022-09-15 +date: 2022-09-19 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 6505a776c755f..db310d20c58c2 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: 2022-09-15 +date: 2022-09-19 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 1aaa04d80969d..807d993ef3133 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: 2022-09-15 +date: 2022-09-19 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 dbb91981c1ee4..c31121fbc086b 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: 2022-09-15 +date: 2022-09-19 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 9b9795b90b6e2..dce641084de13 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: 2022-09-15 +date: 2022-09-19 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_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index a5a2e27a9a444..7ad26210b5492 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: 2022-09-15 +date: 2022-09-19 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 d71ab586a5d39..2a7591021ba8e 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: 2022-09-15 +date: 2022-09-19 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 51caf2596740a..72e272801b088 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: 2022-09-15 +date: 2022-09-19 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_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 65e93ca8934a0..e020ec6af3202 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: 2022-09-15 +date: 2022-09-19 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 8231e88b70f3c..761049aee440a 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: 2022-09-15 +date: 2022-09-19 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 cb8bfa88b8280..1ffc44b6ef8b2 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: 2022-09-15 +date: 2022-09-19 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 ad7723a079ddd..e7e2ac7cb4742 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: 2022-09-15 +date: 2022-09-19 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 60173859724df..2197b5a0c5863 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: 2022-09-15 +date: 2022-09-19 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 be28c7633b345..61188e8210cf6 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: 2022-09-15 +date: 2022-09-19 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 98b9923c0dfdc..7fddba4ee207c 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: 2022-09-15 +date: 2022-09-19 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 8ca618b072421..1c463947f1554 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: 2022-09-15 +date: 2022-09-19 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 e4ddbece2eeff..52b683a980720 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: 2022-09-15 +date: 2022-09-19 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.mdx b/api_docs/kbn_core_saved_objects_common.mdx index d59a9bc3799bf..b0345cb975f1c 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: 2022-09-15 +date: 2022-09-19 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 49a9a9b851e76..d8fb0828c456c 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: 2022-09-15 +date: 2022-09-19 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 f190deef5734d..6ab8e2c871f8c 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json index 0ff9be0a9591b..3e01e7bbd7649 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.devdocs.json @@ -1757,7 +1757,7 @@ "label": "client", "description": [], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -1875,7 +1875,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", 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 6556d0aa06a80..13dcb52b07bfc 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: 2022-09-15 +date: 2022-09-19 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 de7e013de53d6..8d0beb25f45d5 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: 2022-09-15 +date: 2022-09-19 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 e0baaddc9e3a8..df8753330255e 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: 2022-09-15 +date: 2022-09-19 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 216f07224840f..a3b32ceac3b7d 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: 2022-09-15 +date: 2022-09-19 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 ec53c99e25a9d..707407dd7004d 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: 2022-09-15 +date: 2022-09-19 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 ec2a60fd65ecf..d2d754456d37f 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_core_status_common.devdocs.json index b49607b86963e..10cab0bc06263 100644 --- a/api_docs/kbn_core_status_common.devdocs.json +++ b/api_docs/kbn_core_status_common.devdocs.json @@ -211,7 +211,7 @@ "\nPossible values for the ID of a {@link ServiceStatusLevel}\n" ], "signature": [ - "\"critical\" | \"degraded\" | \"unavailable\" | \"available\"" + "\"degraded\" | \"unavailable\" | \"available\" | \"critical\"" ], "path": "packages/core/status/core-status-common/src/service_status.ts", "deprecated": false, diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 56412198db1ae..c9f2e65ab67c3 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_core_status_common_internal.devdocs.json index 39b4d63b9e25a..eba46bfede8b3 100644 --- a/api_docs/kbn_core_status_common_internal.devdocs.json +++ b/api_docs/kbn_core_status_common_internal.devdocs.json @@ -195,7 +195,7 @@ "label": "level", "description": [], "signature": [ - "\"critical\" | \"degraded\" | \"unavailable\" | \"available\"" + "\"degraded\" | \"unavailable\" | \"available\" | \"critical\"" ], "path": "packages/core/status/core-status-common-internal/src/status.ts", "deprecated": false, diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index fdf64c71ff872..488b3c8a2605a 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_core_status_server.devdocs.json index 6438e27aa69d3..5ec5b6bbeb967 100644 --- a/api_docs/kbn_core_status_server.devdocs.json +++ b/api_docs/kbn_core_status_server.devdocs.json @@ -339,7 +339,7 @@ "\nPossible values for the ID of a {@link ServiceStatusLevel}\n" ], "signature": [ - "\"critical\" | \"degraded\" | \"unavailable\" | \"available\"" + "\"degraded\" | \"unavailable\" | \"available\" | \"critical\"" ], "path": "node_modules/@types/kbn__core-status-common/index.d.ts", "deprecated": false, diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index 891adbc290582..0b27508c68edd 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: 2022-09-15 +date: 2022-09-19 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 0688457bb68dd..9a0057e42b986 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: 2022-09-15 +date: 2022-09-19 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 9f5672c49fa9a..0dbe9e78950f7 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: 2022-09-15 +date: 2022-09-19 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 cf3482bcb5633..1523d612a7837 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: 2022-09-15 +date: 2022-09-19 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 2eb20322031d5..0c6f38bf54a27 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: 2022-09-15 +date: 2022-09-19 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_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 5107fce867997..1869e29fb1d2b 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: 2022-09-15 +date: 2022-09-19 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 ab60f876fb239..b0233f028d7da 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: 2022-09-15 +date: 2022-09-19 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 5ef16629223aa..c62f97e17719b 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: 2022-09-15 +date: 2022-09-19 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 43eb2a53e8e40..0274ba8b5e69b 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: 2022-09-15 +date: 2022-09-19 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 f5427a1d24980..36c149be6a180 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: 2022-09-15 +date: 2022-09-19 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 61ad6eb332fc6..3bb84e0a077d6 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_core_ui_settings_common.devdocs.json index 6a5e89a290d41..6967cb7263b52 100644 --- a/api_docs/kbn_core_ui_settings_common.devdocs.json +++ b/api_docs/kbn_core_ui_settings_common.devdocs.json @@ -395,7 +395,7 @@ "\nA sub-set of {@link UiSettingsParams} exposed to the client-side." ], "signature": [ - "{ options?: string[] | number[] | undefined; type?: ", + "{ name?: string | undefined; value?: unknown; description?: string | undefined; category?: string[] | undefined; options?: string[] | number[] | undefined; optionLabels?: Record | undefined; requiresPageReload?: boolean | undefined; readonly?: boolean | undefined; sensitive?: boolean | undefined; type?: ", { "pluginId": "@kbn/core-ui-settings-common", "scope": "common", @@ -403,7 +403,7 @@ "section": "def-common.UiSettingsType", "text": "UiSettingsType" }, - " | undefined; metric?: { type: string; name: string; } | undefined; value?: unknown; description?: string | undefined; name?: string | undefined; order?: number | undefined; category?: string[] | undefined; optionLabels?: Record | undefined; requiresPageReload?: boolean | undefined; readonly?: boolean | undefined; sensitive?: boolean | undefined; deprecation?: ", + " | undefined; deprecation?: ", { "pluginId": "@kbn/core-ui-settings-common", "scope": "common", @@ -411,7 +411,7 @@ "section": "def-common.DeprecationSettings", "text": "DeprecationSettings" }, - " | undefined; }" + " | undefined; order?: number | undefined; metric?: { type: string; name: string; } | undefined; }" ], "path": "packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts", "deprecated": false, diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 47b5af84acfb6..36179e35cda0e 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: 2022-09-15 +date: 2022-09-19 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 329168fd9bbde..9fac63e1d19e7 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: 2022-09-15 +date: 2022-09-19 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 986877ed698e4..7c2966c9a7b97 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: 2022-09-15 +date: 2022-09-19 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 51d5271583140..2917ed2413679 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: 2022-09-15 +date: 2022-09-19 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 62ac9fa1d11c6..8511c4d60c8f0 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: 2022-09-15 +date: 2022-09-19 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 5b81fa3b07461..f39dc7ab050c2 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: 2022-09-15 +date: 2022-09-19 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 544a8d745db23..39fe406cbf031 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: 2022-09-15 +date: 2022-09-19 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 c3ac631270416..c50536bf58218 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: 2022-09-15 +date: 2022-09-19 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 507fd1bf0960d..12f73977cba86 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index d3e73af45bad3..356818ed5f36c 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: 2022-09-15 +date: 2022-09-19 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 50b7c4cd41f60..626e9a0fcc7bd 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: 2022-09-15 +date: 2022-09-19 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 9d5e505e7951d..3ef66e2d8aeec 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: 2022-09-15 +date: 2022-09-19 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 4638fe923ac2e..692f57dff7d9e 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: 2022-09-15 +date: 2022-09-19 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 4fcade43d5d29..f93c29b319dea 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 9811a7bb5bb93..17579aee87fd5 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: 2022-09-15 +date: 2022-09-19 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 bcb5ea688e245..c0b2e012e728e 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: 2022-09-15 +date: 2022-09-19 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 20e7a51b97546..ad69eb609008b 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 94c83eff7d1be..17480753e686b 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: 2022-09-15 +date: 2022-09-19 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 19bd3f2ac7359..35c26c35c9dfc 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: 2022-09-15 +date: 2022-09-19 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 928f09a685e64..d84edf5ed8652 100644 --- a/api_docs/kbn_es_query.devdocs.json +++ b/api_docs/kbn_es_query.devdocs.json @@ -4331,7 +4331,7 @@ "section": "def-common.IFieldSubType", "text": "IFieldSubType" }, - " | undefined; script?: string | undefined; lang?: string | undefined; scripted?: boolean | undefined; }" + " | undefined; script?: string | undefined; lang?: string | undefined; scripted?: boolean | undefined; esTypes?: string[] | undefined; }" ], "path": "packages/kbn-es-query/src/es_query/types.ts", "deprecated": false, diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 1cd286490d974..aa1a45fcb68ae 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 89f27af7fc60e..46f816dd62135 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: 2022-09-15 +date: 2022-09-19 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 a0c4cddfbd6a3..b588da3be6228 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: 2022-09-15 +date: 2022-09-19 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 e08e5fe26bcb2..0b990055b579b 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: 2022-09-15 +date: 2022-09-19 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_generate.mdx b/api_docs/kbn_generate.mdx index f457f495084aa..e213382426a76 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: 2022-09-15 +date: 2022-09-19 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 57a7ac01e3648..763050b79c962 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/get-repo-files'] --- import kbnGetRepoFilesObj from './kbn_get_repo_files.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 3c4fc0921e654..2daaf33255549 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: 2022-09-15 +date: 2022-09-19 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 4686d195d85e1..bbe1e97fe4131 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.devdocs.json b/api_docs/kbn_home_sample_data_card.devdocs.json index 729ea01b9dc8e..7bc80108253eb 100644 --- a/api_docs/kbn_home_sample_data_card.devdocs.json +++ b/api_docs/kbn_home_sample_data_card.devdocs.json @@ -633,7 +633,7 @@ "\nParameters drawn from the Storybook arguments collection that customize a component story." ], "signature": [ - "{ status: any; description: any; name: any; includeAppLinks: any; simulateErrors: any; }" + "{ name: any; description: any; status: any; includeAppLinks: any; simulateErrors: any; }" ], "path": "packages/home/sample_data_card/src/mocks/index.ts", "deprecated": false, diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 6b3cf035e39fa..120794f11271e 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: 2022-09-15 +date: 2022-09-19 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 78e6f4004e420..b6e2fa6aa6a4a 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: 2022-09-15 +date: 2022-09-19 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 be63434e17c8e..3eedaa3fe216b 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 8fbbf0b59a3e8..1fa3617bfd175 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: 2022-09-15 +date: 2022-09-19 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 48cf86699c924..6d923c8b57f44 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: 2022-09-15 +date: 2022-09-19 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 a0d7f2e808639..3c6223096886a 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: 2022-09-15 +date: 2022-09-19 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 ccdf88e0e2704..a383cf7591cb7 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 8ff2bd9cc7ad9..d5f0fc06da61f 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 7d0e427c1a41d..bcd5272b67ffb 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: 2022-09-15 +date: 2022-09-19 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 f2dc1232019c7..996dbf861d989 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: 2022-09-15 +date: 2022-09-19 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 3e769b57f3f81..b14359bb4fad6 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: 2022-09-15 +date: 2022-09-19 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 eb118e41d9b4a..06d6b1da8681a 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: 2022-09-15 +date: 2022-09-19 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 c202f85e96ab8..de1260af1c1bc 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: 2022-09-15 +date: 2022-09-19 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_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 295e6f7e9d19b..7852eb26ac349 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: 2022-09-15 +date: 2022-09-19 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_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index d917e3d8468d1..d0a12054e9367 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 132f0a3c5bbdd..458c954565ad6 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: 2022-09-15 +date: 2022-09-19 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 d64a565dfa64d..517cb2fb3d593 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: 2022-09-15 +date: 2022-09-19 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 a035daa894432..1b23888838c8f 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index b3a81b42f77f5..d6eeba5dfc4a2 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: 2022-09-15 +date: 2022-09-19 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 88a210a9a646b..2460bdc0064f1 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: 2022-09-15 +date: 2022-09-19 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 1daf033ca32ce..f90d5d93543c3 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: 2022-09-15 +date: 2022-09-19 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 01e098b4e9f3a..05464fe2d2a16 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 6b9ab93f09609..31105d93eb76e 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.devdocs.json b/api_docs/kbn_rule_data_utils.devdocs.json index 4323a179a30df..c30477647a1ce 100644 --- a/api_docs/kbn_rule_data_utils.devdocs.json +++ b/api_docs/kbn_rule_data_utils.devdocs.json @@ -912,7 +912,7 @@ "label": "AlertConsumers", "description": [], "signature": [ - "\"logs\" | \"apm\" | \"uptime\" | \"siem\" | \"infrastructure\" | \"observability\"" + "\"observability\" | \"logs\" | \"apm\" | \"uptime\" | \"siem\" | \"infrastructure\"" ], "path": "packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts", "deprecated": false, @@ -1107,7 +1107,7 @@ "label": "ValidFeatureId", "description": [], "signature": [ - "\"logs\" | \"apm\" | \"uptime\" | \"siem\" | \"infrastructure\" | \"observability\"" + "\"observability\" | \"logs\" | \"apm\" | \"uptime\" | \"siem\" | \"infrastructure\"" ], "path": "packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts", "deprecated": false, diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index f172406667175..3ca2046b79033 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index b1fd1ddfe4903..cc2ca2c9d734e 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.devdocs.json b/api_docs/kbn_securitysolution_es_utils.devdocs.json index b3149b45f10dc..a96bb0f4d470e 100644 --- a/api_docs/kbn_securitysolution_es_utils.devdocs.json +++ b/api_docs/kbn_securitysolution_es_utils.devdocs.json @@ -461,7 +461,7 @@ "label": "esClient", "description": [], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -579,7 +579,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", @@ -1707,7 +1707,7 @@ "label": "esClient", "description": [], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -1825,7 +1825,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 66dafe9c6b3b5..dfe3c9f177c65 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: 2022-09-15 +date: 2022-09-19 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_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index c5193b1791ab7..5c0817d2e4aa9 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: 2022-09-15 +date: 2022-09-19 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 b281c7e8f48f5..e6b75cfaf109d 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_securitysolution_io_ts_list_types.devdocs.json index f75157365c883..64c89d491e6b6 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.devdocs.json +++ b/api_docs/kbn_securitysolution_io_ts_list_types.devdocs.json @@ -2168,7 +2168,7 @@ "label": "CreateListSchemaDecoded", "description": [], "signature": [ - "{ type: \"boolean\" | \"date\" | \"keyword\" | \"ip\" | \"text\" | \"geo_point\" | \"geo_shape\" | \"date_nanos\" | \"long\" | \"double\" | \"date_range\" | \"ip_range\" | \"shape\" | \"short\" | \"binary\" | \"float\" | \"half_float\" | \"integer\" | \"byte\" | \"long_range\" | \"integer_range\" | \"float_range\" | \"double_range\"; id: string | undefined; description: string; name: string; meta: object | undefined; serializer: string | undefined; deserializer: string | undefined; } & { version: number; }" + "{ name: string; description: string; type: \"boolean\" | \"date\" | \"keyword\" | \"ip\" | \"text\" | \"geo_point\" | \"geo_shape\" | \"date_nanos\" | \"long\" | \"double\" | \"date_range\" | \"ip_range\" | \"shape\" | \"short\" | \"binary\" | \"float\" | \"half_float\" | \"integer\" | \"byte\" | \"long_range\" | \"integer_range\" | \"float_range\" | \"double_range\"; id: string | undefined; meta: object | undefined; serializer: string | undefined; deserializer: string | undefined; } & { version: number; }" ], "path": "packages/kbn-securitysolution-io-ts-list-types/src/request/create_list_schema/index.ts", "deprecated": false, diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index eef24273b1e07..12f99ffdb86f8 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: 2022-09-15 +date: 2022-09-19 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 8ef2c96c575c7..d65fdc91a4c3c 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: 2022-09-15 +date: 2022-09-19 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 64fcb546d398e..11a9c5dedfdac 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: 2022-09-15 +date: 2022-09-19 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 690abfd693122..bfe4914773e8f 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: 2022-09-15 +date: 2022-09-19 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 7686c02430098..186c83d4ff977 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: 2022-09-15 +date: 2022-09-19 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 6c09251d69863..76c559246ccd4 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: 2022-09-15 +date: 2022-09-19 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 91755aa023325..8b4da1d697f90 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: 2022-09-15 +date: 2022-09-19 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 c7ab1e58f32c9..824af05b6e214 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: 2022-09-15 +date: 2022-09-19 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 ef669c9c76ff3..199bf0dfd74b9 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: 2022-09-15 +date: 2022-09-19 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 32f52a9162e63..68fd197ec7eb5 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: 2022-09-15 +date: 2022-09-19 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 dee2e6d8758ef..8938c74261d4f 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: 2022-09-15 +date: 2022-09-19 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 c06c3f7785327..009b8a1950acb 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: 2022-09-15 +date: 2022-09-19 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 bde4f2fa741bb..c597bf2642663 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.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 515d59019bd90..729e2e9531990 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: 2022-09-15 +date: 2022-09-19 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 e569c371b93ee..2af32012cb350 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_shared_ux_card_no_data.devdocs.json index ce63cea38e22e..a97e211368c5c 100644 --- a/api_docs/kbn_shared_ux_card_no_data.devdocs.json +++ b/api_docs/kbn_shared_ux_card_no_data.devdocs.json @@ -148,7 +148,7 @@ "signature": [ "Partial> & { button?: React.ReactNode; onClick?: React.MouseEventHandler | undefined; description?: React.ReactNode; category?: string | undefined; canAccessFleet?: boolean | undefined; }" + ", \"description\" | \"onClick\" | \"isDisabled\" | \"button\" | \"layout\">> & { button?: React.ReactNode; onClick?: React.MouseEventHandler | undefined; description?: React.ReactNode; category?: string | undefined; canAccessFleet?: boolean | undefined; }" ], "path": "node_modules/@kbn/shared-ux-card-no-data-types/index.d.ts", "deprecated": false, @@ -184,9 +184,9 @@ "\nProps for the `NoDataCard` sevice-connected component." ], "signature": [ - "{ children?: React.ReactNode; onError?: React.ReactEventHandler | undefined; hidden?: boolean | undefined; icon?: React.ReactElement<", + "{ children?: React.ReactNode; description?: React.ReactNode; category?: string | undefined; onError?: React.ReactEventHandler | undefined; hidden?: boolean | undefined; icon?: React.ReactElement<", "EuiIconProps", - ", string | React.JSXElementConstructor> | null | undefined; id?: string | undefined; image?: string | React.ReactElement> | undefined; className?: string | undefined; title?: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | undefined; onChange?: React.FormEventHandler | undefined; onKeyDown?: React.KeyboardEventHandler | undefined; onClick?: React.MouseEventHandler | undefined; description?: React.ReactNode; security?: string | undefined; defaultValue?: string | number | readonly string[] | undefined; lang?: string | undefined; category?: string | undefined; defaultChecked?: boolean | undefined; suppressContentEditableWarning?: boolean | undefined; suppressHydrationWarning?: boolean | undefined; accessKey?: string | undefined; contentEditable?: \"inherit\" | Booleanish | undefined; contextMenu?: string | undefined; dir?: string | undefined; draggable?: Booleanish | undefined; placeholder?: string | undefined; slot?: string | undefined; spellCheck?: Booleanish | undefined; style?: React.CSSProperties | undefined; tabIndex?: number | undefined; translate?: \"no\" | \"yes\" | undefined; radioGroup?: string | undefined; role?: React.AriaRole | undefined; about?: string | undefined; datatype?: string | undefined; inlist?: any; prefix?: string | undefined; property?: string | undefined; resource?: string | undefined; typeof?: string | undefined; vocab?: string | undefined; autoCapitalize?: string | undefined; autoCorrect?: string | undefined; autoSave?: string | undefined; itemProp?: string | undefined; itemScope?: boolean | undefined; itemType?: string | undefined; itemID?: string | undefined; itemRef?: string | undefined; results?: number | undefined; unselectable?: \"on\" | \"off\" | undefined; inputMode?: \"none\" | \"email\" | \"search\" | \"text\" | \"tel\" | \"url\" | \"numeric\" | \"decimal\" | undefined; is?: string | undefined; 'aria-activedescendant'?: string | undefined; 'aria-atomic'?: Booleanish | undefined; 'aria-autocomplete'?: \"none\" | \"list\" | \"inline\" | \"both\" | undefined; 'aria-busy'?: Booleanish | undefined; 'aria-checked'?: boolean | \"mixed\" | \"false\" | \"true\" | undefined; 'aria-colcount'?: number | undefined; 'aria-colindex'?: number | undefined; 'aria-colspan'?: number | undefined; 'aria-controls'?: string | undefined; 'aria-current'?: boolean | \"date\" | \"location\" | \"time\" | \"page\" | \"false\" | \"true\" | \"step\" | undefined; 'aria-describedby'?: string | undefined; 'aria-details'?: string | undefined; 'aria-disabled'?: Booleanish | undefined; 'aria-dropeffect'?: \"none\" | \"copy\" | \"link\" | \"execute\" | \"move\" | \"popup\" | undefined; 'aria-errormessage'?: string | undefined; 'aria-expanded'?: Booleanish | undefined; 'aria-flowto'?: string | undefined; 'aria-grabbed'?: Booleanish | undefined; 'aria-haspopup'?: boolean | \"grid\" | \"menu\" | \"false\" | \"true\" | \"dialog\" | \"listbox\" | \"tree\" | undefined; 'aria-hidden'?: Booleanish | undefined; 'aria-invalid'?: boolean | \"false\" | \"true\" | \"grammar\" | \"spelling\" | undefined; 'aria-keyshortcuts'?: string | undefined; 'aria-label'?: string | undefined; 'aria-labelledby'?: string | undefined; 'aria-level'?: number | undefined; 'aria-live'?: \"off\" | \"assertive\" | \"polite\" | undefined; 'aria-modal'?: Booleanish | undefined; 'aria-multiline'?: Booleanish | undefined; 'aria-multiselectable'?: Booleanish | undefined; 'aria-orientation'?: \"horizontal\" | \"vertical\" | undefined; 'aria-owns'?: string | undefined; 'aria-placeholder'?: string | undefined; 'aria-posinset'?: number | undefined; 'aria-pressed'?: boolean | \"mixed\" | \"false\" | \"true\" | undefined; 'aria-readonly'?: Booleanish | undefined; 'aria-relevant'?: \"all\" | \"text\" | \"additions\" | \"additions removals\" | \"additions text\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\" | undefined; 'aria-required'?: Booleanish | undefined; 'aria-roledescription'?: string | undefined; 'aria-rowcount'?: number | undefined; 'aria-rowindex'?: number | undefined; 'aria-rowspan'?: number | undefined; 'aria-selected'?: Booleanish | undefined; 'aria-setsize'?: number | undefined; 'aria-sort'?: \"none\" | \"other\" | \"ascending\" | \"descending\" | undefined; 'aria-valuemax'?: number | undefined; 'aria-valuemin'?: number | undefined; 'aria-valuenow'?: number | undefined; 'aria-valuetext'?: string | undefined; dangerouslySetInnerHTML?: { __html: string; } | undefined; onCopy?: React.ClipboardEventHandler | undefined; onCopyCapture?: React.ClipboardEventHandler | undefined; onCut?: React.ClipboardEventHandler | undefined; onCutCapture?: React.ClipboardEventHandler | undefined; onPaste?: React.ClipboardEventHandler | undefined; onPasteCapture?: React.ClipboardEventHandler | undefined; onCompositionEnd?: React.CompositionEventHandler | undefined; onCompositionEndCapture?: React.CompositionEventHandler | undefined; onCompositionStart?: React.CompositionEventHandler | undefined; onCompositionStartCapture?: React.CompositionEventHandler | undefined; onCompositionUpdate?: React.CompositionEventHandler | undefined; onCompositionUpdateCapture?: React.CompositionEventHandler | undefined; onFocus?: React.FocusEventHandler | undefined; onFocusCapture?: React.FocusEventHandler | undefined; onBlur?: React.FocusEventHandler | undefined; onBlurCapture?: React.FocusEventHandler | undefined; onChangeCapture?: React.FormEventHandler | undefined; onBeforeInput?: React.FormEventHandler | undefined; onBeforeInputCapture?: React.FormEventHandler | undefined; onInput?: React.FormEventHandler | undefined; onInputCapture?: React.FormEventHandler | undefined; onReset?: React.FormEventHandler | undefined; onResetCapture?: React.FormEventHandler | undefined; onSubmit?: React.FormEventHandler | undefined; onSubmitCapture?: React.FormEventHandler | undefined; onInvalid?: React.FormEventHandler | undefined; onInvalidCapture?: React.FormEventHandler | undefined; onLoad?: React.ReactEventHandler | undefined; onLoadCapture?: React.ReactEventHandler | undefined; onErrorCapture?: React.ReactEventHandler | undefined; onKeyDownCapture?: React.KeyboardEventHandler | undefined; onKeyPress?: React.KeyboardEventHandler | undefined; onKeyPressCapture?: React.KeyboardEventHandler | undefined; onKeyUp?: React.KeyboardEventHandler | undefined; onKeyUpCapture?: React.KeyboardEventHandler | undefined; onAbort?: React.ReactEventHandler | undefined; onAbortCapture?: React.ReactEventHandler | undefined; onCanPlay?: React.ReactEventHandler | undefined; onCanPlayCapture?: React.ReactEventHandler | undefined; onCanPlayThrough?: React.ReactEventHandler | undefined; onCanPlayThroughCapture?: React.ReactEventHandler | undefined; onDurationChange?: React.ReactEventHandler | undefined; onDurationChangeCapture?: React.ReactEventHandler | undefined; onEmptied?: React.ReactEventHandler | undefined; onEmptiedCapture?: React.ReactEventHandler | undefined; onEncrypted?: React.ReactEventHandler | undefined; onEncryptedCapture?: React.ReactEventHandler | undefined; onEnded?: React.ReactEventHandler | undefined; onEndedCapture?: React.ReactEventHandler | undefined; onLoadedData?: React.ReactEventHandler | undefined; onLoadedDataCapture?: React.ReactEventHandler | undefined; onLoadedMetadata?: React.ReactEventHandler | undefined; onLoadedMetadataCapture?: React.ReactEventHandler | undefined; onLoadStart?: React.ReactEventHandler | undefined; onLoadStartCapture?: React.ReactEventHandler | undefined; onPause?: React.ReactEventHandler | undefined; onPauseCapture?: React.ReactEventHandler | undefined; onPlay?: React.ReactEventHandler | undefined; onPlayCapture?: React.ReactEventHandler | undefined; onPlaying?: React.ReactEventHandler | undefined; onPlayingCapture?: React.ReactEventHandler | undefined; onProgress?: React.ReactEventHandler | undefined; onProgressCapture?: React.ReactEventHandler | undefined; onRateChange?: React.ReactEventHandler | undefined; onRateChangeCapture?: React.ReactEventHandler | undefined; onSeeked?: React.ReactEventHandler | undefined; onSeekedCapture?: React.ReactEventHandler | undefined; onSeeking?: React.ReactEventHandler | undefined; onSeekingCapture?: React.ReactEventHandler | undefined; onStalled?: React.ReactEventHandler | undefined; onStalledCapture?: React.ReactEventHandler | undefined; onSuspend?: React.ReactEventHandler | undefined; onSuspendCapture?: React.ReactEventHandler | undefined; onTimeUpdate?: React.ReactEventHandler | undefined; onTimeUpdateCapture?: React.ReactEventHandler | undefined; onVolumeChange?: React.ReactEventHandler | undefined; onVolumeChangeCapture?: React.ReactEventHandler | undefined; onWaiting?: React.ReactEventHandler | undefined; onWaitingCapture?: React.ReactEventHandler | undefined; onAuxClick?: React.MouseEventHandler | undefined; onAuxClickCapture?: React.MouseEventHandler | undefined; onClickCapture?: React.MouseEventHandler | undefined; onContextMenu?: React.MouseEventHandler | undefined; onContextMenuCapture?: React.MouseEventHandler | undefined; onDoubleClick?: React.MouseEventHandler | undefined; onDoubleClickCapture?: React.MouseEventHandler | undefined; onDrag?: React.DragEventHandler | undefined; onDragCapture?: React.DragEventHandler | undefined; onDragEnd?: React.DragEventHandler | undefined; onDragEndCapture?: React.DragEventHandler | undefined; onDragEnter?: React.DragEventHandler | undefined; onDragEnterCapture?: React.DragEventHandler | undefined; onDragExit?: React.DragEventHandler | undefined; onDragExitCapture?: React.DragEventHandler | undefined; onDragLeave?: React.DragEventHandler | undefined; onDragLeaveCapture?: React.DragEventHandler | undefined; onDragOver?: React.DragEventHandler | undefined; onDragOverCapture?: React.DragEventHandler | undefined; onDragStart?: React.DragEventHandler | undefined; onDragStartCapture?: React.DragEventHandler | undefined; onDrop?: React.DragEventHandler | undefined; onDropCapture?: React.DragEventHandler | undefined; onMouseDown?: React.MouseEventHandler | undefined; onMouseDownCapture?: React.MouseEventHandler | undefined; onMouseEnter?: React.MouseEventHandler | undefined; onMouseLeave?: React.MouseEventHandler | undefined; onMouseMove?: React.MouseEventHandler | undefined; onMouseMoveCapture?: React.MouseEventHandler | undefined; onMouseOut?: React.MouseEventHandler | undefined; onMouseOutCapture?: React.MouseEventHandler | undefined; onMouseOver?: React.MouseEventHandler | undefined; onMouseOverCapture?: React.MouseEventHandler | undefined; onMouseUp?: React.MouseEventHandler | undefined; onMouseUpCapture?: React.MouseEventHandler | undefined; onSelect?: React.ReactEventHandler | undefined; onSelectCapture?: React.ReactEventHandler | undefined; onTouchCancel?: React.TouchEventHandler | undefined; onTouchCancelCapture?: React.TouchEventHandler | undefined; onTouchEnd?: React.TouchEventHandler | undefined; onTouchEndCapture?: React.TouchEventHandler | undefined; onTouchMove?: React.TouchEventHandler | undefined; onTouchMoveCapture?: React.TouchEventHandler | undefined; onTouchStart?: React.TouchEventHandler | undefined; onTouchStartCapture?: React.TouchEventHandler | undefined; onPointerDown?: React.PointerEventHandler | undefined; onPointerDownCapture?: React.PointerEventHandler | undefined; onPointerMove?: React.PointerEventHandler | undefined; onPointerMoveCapture?: React.PointerEventHandler | undefined; onPointerUp?: React.PointerEventHandler | undefined; onPointerUpCapture?: React.PointerEventHandler | undefined; onPointerCancel?: React.PointerEventHandler | undefined; onPointerCancelCapture?: React.PointerEventHandler | undefined; onPointerEnter?: React.PointerEventHandler | undefined; onPointerEnterCapture?: React.PointerEventHandler | undefined; onPointerLeave?: React.PointerEventHandler | undefined; onPointerLeaveCapture?: React.PointerEventHandler | undefined; onPointerOver?: React.PointerEventHandler | undefined; onPointerOverCapture?: React.PointerEventHandler | undefined; onPointerOut?: React.PointerEventHandler | undefined; onPointerOutCapture?: React.PointerEventHandler | undefined; onGotPointerCapture?: React.PointerEventHandler | undefined; onGotPointerCaptureCapture?: React.PointerEventHandler | undefined; onLostPointerCapture?: React.PointerEventHandler | undefined; onLostPointerCaptureCapture?: React.PointerEventHandler | undefined; onScroll?: React.UIEventHandler | undefined; onScrollCapture?: React.UIEventHandler | undefined; onWheel?: React.WheelEventHandler | undefined; onWheelCapture?: React.WheelEventHandler | undefined; onAnimationStart?: React.AnimationEventHandler | undefined; onAnimationStartCapture?: React.AnimationEventHandler | undefined; onAnimationEnd?: React.AnimationEventHandler | undefined; onAnimationEndCapture?: React.AnimationEventHandler | undefined; onAnimationIteration?: React.AnimationEventHandler | undefined; onAnimationIterationCapture?: React.AnimationEventHandler | undefined; onTransitionEnd?: React.TransitionEventHandler | undefined; onTransitionEndCapture?: React.TransitionEventHandler | undefined; 'data-test-subj'?: string | undefined; href?: string | undefined; rel?: string | undefined; target?: string | undefined; paddingSize?: \"none\" | \"m\" | \"s\" | \"xs\" | \"l\" | \"xl\" | undefined; button?: React.ReactNode; footer?: React.ReactNode; hasBorder?: boolean | undefined; textAlign?: CardAlignment | undefined; titleElement?: \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"span\" | undefined; titleSize?: \"s\" | \"xs\" | undefined; betaBadgeProps?: Partial<(", + ", string | React.JSXElementConstructor> | null | undefined; id?: string | undefined; image?: string | React.ReactElement> | undefined; className?: string | undefined; title?: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | undefined; onChange?: React.FormEventHandler | undefined; onKeyDown?: React.KeyboardEventHandler | undefined; onClick?: React.MouseEventHandler | undefined; security?: string | undefined; defaultValue?: string | number | readonly string[] | undefined; lang?: string | undefined; defaultChecked?: boolean | undefined; suppressContentEditableWarning?: boolean | undefined; suppressHydrationWarning?: boolean | undefined; accessKey?: string | undefined; contentEditable?: \"inherit\" | Booleanish | undefined; contextMenu?: string | undefined; dir?: string | undefined; draggable?: Booleanish | undefined; placeholder?: string | undefined; slot?: string | undefined; spellCheck?: Booleanish | undefined; style?: React.CSSProperties | undefined; tabIndex?: number | undefined; translate?: \"no\" | \"yes\" | undefined; radioGroup?: string | undefined; role?: React.AriaRole | undefined; about?: string | undefined; datatype?: string | undefined; inlist?: any; prefix?: string | undefined; property?: string | undefined; resource?: string | undefined; typeof?: string | undefined; vocab?: string | undefined; autoCapitalize?: string | undefined; autoCorrect?: string | undefined; autoSave?: string | undefined; itemProp?: string | undefined; itemScope?: boolean | undefined; itemType?: string | undefined; itemID?: string | undefined; itemRef?: string | undefined; results?: number | undefined; unselectable?: \"on\" | \"off\" | undefined; inputMode?: \"none\" | \"email\" | \"search\" | \"text\" | \"tel\" | \"url\" | \"numeric\" | \"decimal\" | undefined; is?: string | undefined; 'aria-activedescendant'?: string | undefined; 'aria-atomic'?: Booleanish | undefined; 'aria-autocomplete'?: \"none\" | \"list\" | \"inline\" | \"both\" | undefined; 'aria-busy'?: Booleanish | undefined; 'aria-checked'?: boolean | \"mixed\" | \"false\" | \"true\" | undefined; 'aria-colcount'?: number | undefined; 'aria-colindex'?: number | undefined; 'aria-colspan'?: number | undefined; 'aria-controls'?: string | undefined; 'aria-current'?: boolean | \"date\" | \"location\" | \"time\" | \"page\" | \"false\" | \"true\" | \"step\" | undefined; 'aria-describedby'?: string | undefined; 'aria-details'?: string | undefined; 'aria-disabled'?: Booleanish | undefined; 'aria-dropeffect'?: \"none\" | \"copy\" | \"link\" | \"execute\" | \"move\" | \"popup\" | undefined; 'aria-errormessage'?: string | undefined; 'aria-expanded'?: Booleanish | undefined; 'aria-flowto'?: string | undefined; 'aria-grabbed'?: Booleanish | undefined; 'aria-haspopup'?: boolean | \"grid\" | \"menu\" | \"false\" | \"true\" | \"dialog\" | \"listbox\" | \"tree\" | undefined; 'aria-hidden'?: Booleanish | undefined; 'aria-invalid'?: boolean | \"false\" | \"true\" | \"grammar\" | \"spelling\" | undefined; 'aria-keyshortcuts'?: string | undefined; 'aria-label'?: string | undefined; 'aria-labelledby'?: string | undefined; 'aria-level'?: number | undefined; 'aria-live'?: \"off\" | \"assertive\" | \"polite\" | undefined; 'aria-modal'?: Booleanish | undefined; 'aria-multiline'?: Booleanish | undefined; 'aria-multiselectable'?: Booleanish | undefined; 'aria-orientation'?: \"horizontal\" | \"vertical\" | undefined; 'aria-owns'?: string | undefined; 'aria-placeholder'?: string | undefined; 'aria-posinset'?: number | undefined; 'aria-pressed'?: boolean | \"mixed\" | \"false\" | \"true\" | undefined; 'aria-readonly'?: Booleanish | undefined; 'aria-relevant'?: \"all\" | \"text\" | \"additions\" | \"additions removals\" | \"additions text\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\" | undefined; 'aria-required'?: Booleanish | undefined; 'aria-roledescription'?: string | undefined; 'aria-rowcount'?: number | undefined; 'aria-rowindex'?: number | undefined; 'aria-rowspan'?: number | undefined; 'aria-selected'?: Booleanish | undefined; 'aria-setsize'?: number | undefined; 'aria-sort'?: \"none\" | \"other\" | \"ascending\" | \"descending\" | undefined; 'aria-valuemax'?: number | undefined; 'aria-valuemin'?: number | undefined; 'aria-valuenow'?: number | undefined; 'aria-valuetext'?: string | undefined; dangerouslySetInnerHTML?: { __html: string; } | undefined; onCopy?: React.ClipboardEventHandler | undefined; onCopyCapture?: React.ClipboardEventHandler | undefined; onCut?: React.ClipboardEventHandler | undefined; onCutCapture?: React.ClipboardEventHandler | undefined; onPaste?: React.ClipboardEventHandler | undefined; onPasteCapture?: React.ClipboardEventHandler | undefined; onCompositionEnd?: React.CompositionEventHandler | undefined; onCompositionEndCapture?: React.CompositionEventHandler | undefined; onCompositionStart?: React.CompositionEventHandler | undefined; onCompositionStartCapture?: React.CompositionEventHandler | undefined; onCompositionUpdate?: React.CompositionEventHandler | undefined; onCompositionUpdateCapture?: React.CompositionEventHandler | undefined; onFocus?: React.FocusEventHandler | undefined; onFocusCapture?: React.FocusEventHandler | undefined; onBlur?: React.FocusEventHandler | undefined; onBlurCapture?: React.FocusEventHandler | undefined; onChangeCapture?: React.FormEventHandler | undefined; onBeforeInput?: React.FormEventHandler | undefined; onBeforeInputCapture?: React.FormEventHandler | undefined; onInput?: React.FormEventHandler | undefined; onInputCapture?: React.FormEventHandler | undefined; onReset?: React.FormEventHandler | undefined; onResetCapture?: React.FormEventHandler | undefined; onSubmit?: React.FormEventHandler | undefined; onSubmitCapture?: React.FormEventHandler | undefined; onInvalid?: React.FormEventHandler | undefined; onInvalidCapture?: React.FormEventHandler | undefined; onLoad?: React.ReactEventHandler | undefined; onLoadCapture?: React.ReactEventHandler | undefined; onErrorCapture?: React.ReactEventHandler | undefined; onKeyDownCapture?: React.KeyboardEventHandler | undefined; onKeyPress?: React.KeyboardEventHandler | undefined; onKeyPressCapture?: React.KeyboardEventHandler | undefined; onKeyUp?: React.KeyboardEventHandler | undefined; onKeyUpCapture?: React.KeyboardEventHandler | undefined; onAbort?: React.ReactEventHandler | undefined; onAbortCapture?: React.ReactEventHandler | undefined; onCanPlay?: React.ReactEventHandler | undefined; onCanPlayCapture?: React.ReactEventHandler | undefined; onCanPlayThrough?: React.ReactEventHandler | undefined; onCanPlayThroughCapture?: React.ReactEventHandler | undefined; onDurationChange?: React.ReactEventHandler | undefined; onDurationChangeCapture?: React.ReactEventHandler | undefined; onEmptied?: React.ReactEventHandler | undefined; onEmptiedCapture?: React.ReactEventHandler | undefined; onEncrypted?: React.ReactEventHandler | undefined; onEncryptedCapture?: React.ReactEventHandler | undefined; onEnded?: React.ReactEventHandler | undefined; onEndedCapture?: React.ReactEventHandler | undefined; onLoadedData?: React.ReactEventHandler | undefined; onLoadedDataCapture?: React.ReactEventHandler | undefined; onLoadedMetadata?: React.ReactEventHandler | undefined; onLoadedMetadataCapture?: React.ReactEventHandler | undefined; onLoadStart?: React.ReactEventHandler | undefined; onLoadStartCapture?: React.ReactEventHandler | undefined; onPause?: React.ReactEventHandler | undefined; onPauseCapture?: React.ReactEventHandler | undefined; onPlay?: React.ReactEventHandler | undefined; onPlayCapture?: React.ReactEventHandler | undefined; onPlaying?: React.ReactEventHandler | undefined; onPlayingCapture?: React.ReactEventHandler | undefined; onProgress?: React.ReactEventHandler | undefined; onProgressCapture?: React.ReactEventHandler | undefined; onRateChange?: React.ReactEventHandler | undefined; onRateChangeCapture?: React.ReactEventHandler | undefined; onSeeked?: React.ReactEventHandler | undefined; onSeekedCapture?: React.ReactEventHandler | undefined; onSeeking?: React.ReactEventHandler | undefined; onSeekingCapture?: React.ReactEventHandler | undefined; onStalled?: React.ReactEventHandler | undefined; onStalledCapture?: React.ReactEventHandler | undefined; onSuspend?: React.ReactEventHandler | undefined; onSuspendCapture?: React.ReactEventHandler | undefined; onTimeUpdate?: React.ReactEventHandler | undefined; onTimeUpdateCapture?: React.ReactEventHandler | undefined; onVolumeChange?: React.ReactEventHandler | undefined; onVolumeChangeCapture?: React.ReactEventHandler | undefined; onWaiting?: React.ReactEventHandler | undefined; onWaitingCapture?: React.ReactEventHandler | undefined; onAuxClick?: React.MouseEventHandler | undefined; onAuxClickCapture?: React.MouseEventHandler | undefined; onClickCapture?: React.MouseEventHandler | undefined; onContextMenu?: React.MouseEventHandler | undefined; onContextMenuCapture?: React.MouseEventHandler | undefined; onDoubleClick?: React.MouseEventHandler | undefined; onDoubleClickCapture?: React.MouseEventHandler | undefined; onDrag?: React.DragEventHandler | undefined; onDragCapture?: React.DragEventHandler | undefined; onDragEnd?: React.DragEventHandler | undefined; onDragEndCapture?: React.DragEventHandler | undefined; onDragEnter?: React.DragEventHandler | undefined; onDragEnterCapture?: React.DragEventHandler | undefined; onDragExit?: React.DragEventHandler | undefined; onDragExitCapture?: React.DragEventHandler | undefined; onDragLeave?: React.DragEventHandler | undefined; onDragLeaveCapture?: React.DragEventHandler | undefined; onDragOver?: React.DragEventHandler | undefined; onDragOverCapture?: React.DragEventHandler | undefined; onDragStart?: React.DragEventHandler | undefined; onDragStartCapture?: React.DragEventHandler | undefined; onDrop?: React.DragEventHandler | undefined; onDropCapture?: React.DragEventHandler | undefined; onMouseDown?: React.MouseEventHandler | undefined; onMouseDownCapture?: React.MouseEventHandler | undefined; onMouseEnter?: React.MouseEventHandler | undefined; onMouseLeave?: React.MouseEventHandler | undefined; onMouseMove?: React.MouseEventHandler | undefined; onMouseMoveCapture?: React.MouseEventHandler | undefined; onMouseOut?: React.MouseEventHandler | undefined; onMouseOutCapture?: React.MouseEventHandler | undefined; onMouseOver?: React.MouseEventHandler | undefined; onMouseOverCapture?: React.MouseEventHandler | undefined; onMouseUp?: React.MouseEventHandler | undefined; onMouseUpCapture?: React.MouseEventHandler | undefined; onSelect?: React.ReactEventHandler | undefined; onSelectCapture?: React.ReactEventHandler | undefined; onTouchCancel?: React.TouchEventHandler | undefined; onTouchCancelCapture?: React.TouchEventHandler | undefined; onTouchEnd?: React.TouchEventHandler | undefined; onTouchEndCapture?: React.TouchEventHandler | undefined; onTouchMove?: React.TouchEventHandler | undefined; onTouchMoveCapture?: React.TouchEventHandler | undefined; onTouchStart?: React.TouchEventHandler | undefined; onTouchStartCapture?: React.TouchEventHandler | undefined; onPointerDown?: React.PointerEventHandler | undefined; onPointerDownCapture?: React.PointerEventHandler | undefined; onPointerMove?: React.PointerEventHandler | undefined; onPointerMoveCapture?: React.PointerEventHandler | undefined; onPointerUp?: React.PointerEventHandler | undefined; onPointerUpCapture?: React.PointerEventHandler | undefined; onPointerCancel?: React.PointerEventHandler | undefined; onPointerCancelCapture?: React.PointerEventHandler | undefined; onPointerEnter?: React.PointerEventHandler | undefined; onPointerEnterCapture?: React.PointerEventHandler | undefined; onPointerLeave?: React.PointerEventHandler | undefined; onPointerLeaveCapture?: React.PointerEventHandler | undefined; onPointerOver?: React.PointerEventHandler | undefined; onPointerOverCapture?: React.PointerEventHandler | undefined; onPointerOut?: React.PointerEventHandler | undefined; onPointerOutCapture?: React.PointerEventHandler | undefined; onGotPointerCapture?: React.PointerEventHandler | undefined; onGotPointerCaptureCapture?: React.PointerEventHandler | undefined; onLostPointerCapture?: React.PointerEventHandler | undefined; onLostPointerCaptureCapture?: React.PointerEventHandler | undefined; onScroll?: React.UIEventHandler | undefined; onScrollCapture?: React.UIEventHandler | undefined; onWheel?: React.WheelEventHandler | undefined; onWheelCapture?: React.WheelEventHandler | undefined; onAnimationStart?: React.AnimationEventHandler | undefined; onAnimationStartCapture?: React.AnimationEventHandler | undefined; onAnimationEnd?: React.AnimationEventHandler | undefined; onAnimationEndCapture?: React.AnimationEventHandler | undefined; onAnimationIteration?: React.AnimationEventHandler | undefined; onAnimationIterationCapture?: React.AnimationEventHandler | undefined; onTransitionEnd?: React.TransitionEventHandler | undefined; onTransitionEndCapture?: React.TransitionEventHandler | undefined; 'data-test-subj'?: string | undefined; href?: string | undefined; rel?: string | undefined; target?: string | undefined; paddingSize?: \"none\" | \"m\" | \"s\" | \"xs\" | \"l\" | \"xl\" | undefined; button?: React.ReactNode; footer?: React.ReactNode; hasBorder?: boolean | undefined; textAlign?: CardAlignment | undefined; titleElement?: \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"span\" | undefined; titleSize?: \"s\" | \"xs\" | undefined; betaBadgeProps?: Partial<(", "CommonProps", " & ", "DisambiguateSet", diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index ab7ceb2be4730..4702ae356277c 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_shared_ux_card_no_data_mocks.devdocs.json index bbd3d2f518bff..c83e2d8f0e1aa 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.devdocs.json +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.devdocs.json @@ -509,7 +509,7 @@ "\nStorybook parameters provided from the controls addon." ], "signature": [ - "{ title: any; description: any; category: any; button: any; canAccessFleet: any; }" + "{ description: any; category: any; title: any; button: any; canAccessFleet: any; }" ], "path": "packages/shared-ux/card/no_data/mocks/src/storybook.ts", "deprecated": false, 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 08416e4fe70ae..d2fa0b6b4595d 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: 2022-09-15 +date: 2022-09-19 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_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index a7a69570eff79..62db567f3b5b6 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: 2022-09-15 +date: 2022-09-19 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_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index d215293450495..a145dcbdb058f 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: 2022-09-15 +date: 2022-09-19 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.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index 30371fcc7755c..ed9f0d42a0543 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: 2022-09-15 +date: 2022-09-19 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'; 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 1828691b1f733..c37e0c0dbb0ae 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: 2022-09-15 +date: 2022-09-19 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.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 48b144ff6b98e..cb81d18958901 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: 2022-09-15 +date: 2022-09-19 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 adfee4a1fd884..2df2b74c94da7 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: 2022-09-15 +date: 2022-09-19 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 2004e5e24232b..7d294b40b381f 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: 2022-09-15 +date: 2022-09-19 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 b1553e55a8298..f85f7077c84fe 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: 2022-09-15 +date: 2022-09-19 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 b80e7b846bd49..25684c8bdf4c4 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: 2022-09-15 +date: 2022-09-19 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 6c5bc6c2fc746..b9479e327b237 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: 2022-09-15 +date: 2022-09-19 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 5add75ded9b23..1f708c075e88c 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: 2022-09-15 +date: 2022-09-19 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 55bc3264893e0..80d394a102640 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: 2022-09-15 +date: 2022-09-19 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 2561840effa5b..a2866a6f4524f 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: 2022-09-15 +date: 2022-09-19 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 254fb90eb5002..a19912d3a3df0 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: 2022-09-15 +date: 2022-09-19 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_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 97e0817bcf8fe..f072025a4cf51 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: 2022-09-15 +date: 2022-09-19 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 1e397273c4ac3..a9e46bf904d80 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: 2022-09-15 +date: 2022-09-19 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 d5bf2e40eb9f3..961bc58fdd077 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: 2022-09-15 +date: 2022-09-19 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 0559dd88ea1af..5398e386a4913 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: 2022-09-15 +date: 2022-09-19 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 3991442b491a8..de7774bc26696 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index ac98d6d8e4faf..5c564fa33e8ab 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index 7244bba46a094..cd6dc31d72879 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-package-json plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] --- import kbnSortPackageJsonObj from './kbn_sort_package_json.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 48f4a15e553b5..3071ecc45f67a 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: 2022-09-15 +date: 2022-09-19 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 a1e56ace2a657..1ebb74d40c591 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: 2022-09-15 +date: 2022-09-19 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 3bd5946ea6b7f..b9282c5b88859 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: 2022-09-15 +date: 2022-09-19 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 065e45d7456d9..de8d564bfadf4 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: 2022-09-15 +date: 2022-09-19 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 51f32682b5615..a2d1023a8dc98 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: 2022-09-15 +date: 2022-09-19 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 fb3ba9a9f97c8..f1cd2fd270fcc 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_test_subj_selector.devdocs.json new file mode 100644 index 0000000000000..78c6606408451 --- /dev/null +++ b/api_docs/kbn_test_subj_selector.devdocs.json @@ -0,0 +1,63 @@ +{ + "id": "@kbn/test-subj-selector", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/test-subj-selector", + "id": "def-server.subj", + "type": "Function", + "tags": [], + "label": "subj", + "description": [ + "\nConverts a testSubject selector into a CSS selector.\n\ntestSubject selector syntax rules:\n\n - `data-test-subj` values can include spaces\n\n - prefixing a value with `~` will allow matching a single word in a `data-test-subj` that uses several space delimited list words\n - example: `~foo`\n - css equivalent: `[data-test-subj~=\"foo\"]`\n\n - the `>` character is used between two values to indicate that the value on the right must match an element inside an element matched by the value on the left\n - example: `foo > bar`\n - css equivalent: `[data-test-subj=foo] [data-test-subj=bar]`\n\n - the `&` character is used between two values to indicate that the value on both sides must both match the element\n - example: `foo & bar`\n - css equivalent: `[data-test-subj=foo][data-test-subj=bar]`" + ], + "signature": [ + "(selector: string) => string" + ], + "path": "packages/kbn-test-subj-selector/test_subj_selector.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/test-subj-selector", + "id": "def-server.subj.$1", + "type": "string", + "tags": [], + "label": "selector", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-test-subj-selector/test_subj_selector.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx new file mode 100644 index 0000000000000..902aebbe1721a --- /dev/null +++ b/api_docs/kbn_test_subj_selector.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: kibKbnTestSubjSelectorPluginApi +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: 2022-09-19 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] +--- +import kbnTestSubjSelectorObj from './kbn_test_subj_selector.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 | 1 | 0 | + +## Server + +### Functions + + diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 40ff79d269ebd..81c9b185ee15c 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index dca175974a18a..a8d37319902b4 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] --- import kbnTypeSummarizerObj from './kbn_type_summarizer.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer_core.mdx b/api_docs/kbn_type_summarizer_core.mdx index 7c51a9cb4590f..9bf08b58d420c 100644 --- a/api_docs/kbn_type_summarizer_core.mdx +++ b/api_docs/kbn_type_summarizer_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer-core title: "@kbn/type-summarizer-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer-core plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer-core'] --- import kbnTypeSummarizerCoreObj from './kbn_type_summarizer_core.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.devdocs.json b/api_docs/kbn_typed_react_router_config.devdocs.json index 016fc28862436..b6c4b0eb31605 100644 --- a/api_docs/kbn_typed_react_router_config.devdocs.json +++ b/api_docs/kbn_typed_react_router_config.devdocs.json @@ -787,12 +787,12 @@ { "parentPluginId": "@kbn/typed-react-router-config", "id": "def-common.RouteMatch.route", - "type": "Uncategorized", + "type": "CompoundType", "tags": [], "label": "route", "description": [], "signature": [ - "TRoute" + "TRoute & { path: string; }" ], "path": "packages/kbn-typed-react-router-config/src/types/index.ts", "deprecated": false, @@ -2177,8 +2177,8 @@ "pluginId": "@kbn/typed-react-router-config", "scope": "common", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Route", - "text": "Route" + "section": "def-common.RouteWithPath", + "text": "RouteWithPath" }, ") => string" ], @@ -2198,8 +2198,8 @@ "pluginId": "@kbn/typed-react-router-config", "scope": "common", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Route", - "text": "Route" + "section": "def-common.RouteWithPath", + "text": "RouteWithPath" } ], "path": "packages/kbn-typed-react-router-config/src/types/index.ts", diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index a5910b5a22a26..999035cb138b9 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: 2022-09-15 +date: 2022-09-19 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_theme.mdx b/api_docs/kbn_ui_theme.mdx index 65eb84dea705b..97dc78902c70c 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: 2022-09-15 +date: 2022-09-19 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.devdocs.json b/api_docs/kbn_user_profile_components.devdocs.json index 849e068eb629e..d8cd0cfd94f9e 100644 --- a/api_docs/kbn_user_profile_components.devdocs.json +++ b/api_docs/kbn_user_profile_components.devdocs.json @@ -319,7 +319,7 @@ }, " extends Omit<", "EuiAvatarProps", - ", \"type\" | \"color\" | \"iconColor\" | \"name\" | \"iconType\" | \"iconSize\" | \"initials\" | \"initialsLength\" | \"imageUrl\">" + ", \"name\" | \"type\" | \"color\" | \"iconColor\" | \"iconType\" | \"iconSize\" | \"initials\" | \"initialsLength\" | \"imageUrl\">" ], "path": "packages/kbn-user-profile-components/src/user_avatar.tsx", "deprecated": false, diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index d8ceff505abb9..d0f66724f05da 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: 2022-09-15 +date: 2022-09-19 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 326dd3ad35567..a56ecedf0080b 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: 2022-09-15 +date: 2022-09-19 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 af43dda9d70c3..a86a480d31aab 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: 2022-09-15 +date: 2022-09-19 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 cb6e6c5f7ce49..02baee6fb676c 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: 2022-09-15 +date: 2022-09-19 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 2630b7f0e85b4..3cb146566fbff 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: 2022-09-15 +date: 2022-09-19 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 b08bc25f9124e..d3e569bc6d95b 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: 2022-09-15 +date: 2022-09-19 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 365499f56d208..6ee12695882dd 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: 2022-09-15 +date: 2022-09-19 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 0dc716bc337b9..466d4a15b17e0 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: 2022-09-15 +date: 2022-09-19 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 f7391fad68e12..26ff516ff228b 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.devdocs.json b/api_docs/lens.devdocs.json index 50822bc6a07fe..17237b208ad45 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -3616,7 +3616,7 @@ "label": "numberDisplay", "description": [], "signature": [ - "\"percent\" | \"hidden\" | \"value\"" + "\"value\" | \"percent\" | \"hidden\"" ], "path": "x-pack/plugins/lens/common/types.ts", "deprecated": false, @@ -5347,76 +5347,6 @@ ], "returnComment": [] }, - { - "parentPluginId": "lens", - "id": "def-public.Visualization.updateLayersConfigurationFromContext", - "type": "Function", - "tags": [], - "label": "updateLayersConfigurationFromContext", - "description": [ - "\nUpdate the configuration for the visualization. This is used to update the state" - ], - "signature": [ - "((props: VisualizationConfigurationFromContextChangeProps) => T) | undefined" - ], - "path": "x-pack/plugins/lens/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "lens", - "id": "def-public.Visualization.updateLayersConfigurationFromContext.$1", - "type": "Object", - "tags": [], - "label": "props", - "description": [], - "signature": [ - "VisualizationConfigurationFromContextChangeProps" - ], - "path": "x-pack/plugins/lens/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "lens", - "id": "def-public.Visualization.getVisualizationSuggestionFromContext", - "type": "Function", - "tags": [], - "label": "getVisualizationSuggestionFromContext", - "description": [ - "\nUpdate the visualization state from the context." - ], - "signature": [ - "((props: VisualizationStateFromContextChangeProps) => ", - "Suggestion", - ") | undefined" - ], - "path": "x-pack/plugins/lens/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "lens", - "id": "def-public.Visualization.getVisualizationSuggestionFromContext.$1", - "type": "Object", - "tags": [], - "label": "props", - "description": [], - "signature": [ - "VisualizationStateFromContextChangeProps" - ], - "path": "x-pack/plugins/lens/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, { "parentPluginId": "lens", "id": "def-public.Visualization.renderDimensionEditor", @@ -6343,6 +6273,40 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.getSuggestionFromConvertToLensContext", + "type": "Function", + "tags": [], + "label": "getSuggestionFromConvertToLensContext", + "description": [], + "signature": [ + "((props: VisualizationStateFromContextChangeProps) => ", + "Suggestion", + ") | undefined" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "lens", + "id": "def-public.Visualization.getSuggestionFromConvertToLensContext.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "VisualizationStateFromContextChangeProps" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "initialIsOpen": false @@ -8162,7 +8126,7 @@ "section": "def-common.GaugeState", "text": "GaugeState" }, - ", \"goal\" | \"min\" | \"metric\" | \"max\"> & { metricAccessor?: string | undefined; minAccessor?: string | undefined; maxAccessor?: string | undefined; goalAccessor?: string | undefined; } & { layerId: string; layerType: ", + ", \"metric\" | \"goal\" | \"min\" | \"max\"> & { metricAccessor?: string | undefined; minAccessor?: string | undefined; maxAccessor?: string | undefined; goalAccessor?: string | undefined; } & { layerId: string; layerType: ", { "pluginId": "lens", "scope": "common", @@ -8311,7 +8275,7 @@ "label": "LensSavedObjectAttributes", "description": [], "signature": [ - "{ title: string; description?: string | undefined; state: { datasourceStates: Record; visualization: unknown; query: ", + "{ description?: string | undefined; title: string; state: { datasourceStates: Record; visualization: unknown; query: ", "Query", "; globalPalette?: { activePaletteId: string; state?: unknown; } | undefined; filters: ", "Filter", @@ -10145,7 +10109,7 @@ "label": "OperationTypePost712", "description": [], "signature": [ - "\"range\" | \"min\" | \"max\" | \"filters\" | \"count\" | \"date_histogram\" | \"percentile\" | \"sum\" | \"average\" | \"terms\" | \"median\" | \"cumulative_sum\" | \"moving_average\" | \"unique_count\" | \"last_value\" | \"counter_rate\" | \"differences\"" + "\"range\" | \"min\" | \"max\" | \"filters\" | \"count\" | \"date_histogram\" | \"percentile\" | \"sum\" | \"average\" | \"terms\" | \"median\" | \"cumulative_sum\" | \"moving_average\" | \"counter_rate\" | \"differences\" | \"unique_count\" | \"last_value\"" ], "path": "x-pack/plugins/lens/server/migrations/types.ts", "deprecated": false, @@ -10160,7 +10124,7 @@ "label": "OperationTypePre712", "description": [], "signature": [ - "\"range\" | \"min\" | \"max\" | \"filters\" | \"count\" | \"date_histogram\" | \"percentile\" | \"sum\" | \"terms\" | \"avg\" | \"median\" | \"cumulative_sum\" | \"derivative\" | \"moving_average\" | \"last_value\" | \"counter_rate\" | \"cardinality\"" + "\"range\" | \"min\" | \"max\" | \"filters\" | \"count\" | \"date_histogram\" | \"percentile\" | \"sum\" | \"terms\" | \"avg\" | \"median\" | \"cumulative_sum\" | \"derivative\" | \"moving_average\" | \"counter_rate\" | \"last_value\" | \"cardinality\"" ], "path": "x-pack/plugins/lens/server/migrations/types.ts", "deprecated": false, @@ -11002,7 +10966,7 @@ "label": "numberDisplay", "description": [], "signature": [ - "\"percent\" | \"hidden\" | \"value\"" + "\"value\" | \"percent\" | \"hidden\"" ], "path": "x-pack/plugins/lens/common/types.ts", "deprecated": false, @@ -11407,7 +11371,7 @@ "label": "NumberDisplayType", "description": [], "signature": [ - "\"percent\" | \"hidden\" | \"value\"" + "\"value\" | \"percent\" | \"hidden\"" ], "path": "x-pack/plugins/lens/common/types.ts", "deprecated": false, diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index e4bb0fe1f7017..9aac73f2cfdc1 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 641 | 0 | 552 | 41 | +| 639 | 0 | 552 | 41 | ## Client diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index fbc64e2a4e6ea..ee4ec9fb3cda8 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: 2022-09-15 +date: 2022-09-19 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 f15740a9a5b68..6f35b4a005143 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.devdocs.json b/api_docs/licensing.devdocs.json index df3f81f908362..0b456fa70029f 100644 --- a/api_docs/licensing.devdocs.json +++ b/api_docs/licensing.devdocs.json @@ -550,6 +550,10 @@ "plugin": "security", "path": "x-pack/plugins/security/common/licensing/license_service.test.ts" }, + { + "plugin": "security", + "path": "x-pack/plugins/security/common/licensing/license_service.test.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/common/license/policy_config.test.ts" @@ -1811,6 +1815,10 @@ "plugin": "security", "path": "x-pack/plugins/security/common/licensing/license_service.test.ts" }, + { + "plugin": "security", + "path": "x-pack/plugins/security/common/licensing/license_service.test.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/common/license/policy_config.test.ts" diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 4419cde10dcd3..b6df7573f3cc3 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.devdocs.json b/api_docs/lists.devdocs.json index fe03f21a11155..c533d58682907 100644 --- a/api_docs/lists.devdocs.json +++ b/api_docs/lists.devdocs.json @@ -3894,7 +3894,7 @@ "label": "esClient", "description": [], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -4012,7 +4012,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 6cb57067ef155..fcda65e552d5e 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: 2022-09-15 +date: 2022-09-19 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 6a35ad4a5349b..c3f29f0bd1b0f 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: 2022-09-15 +date: 2022-09-19 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 2030318dd7315..d82e21faea07b 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: 2022-09-15 +date: 2022-09-19 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 79389095ac6a1..e14518b58ee85 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.devdocs.json b/api_docs/ml.devdocs.json index 5ff2618d25391..29882bb5a3289 100644 --- a/api_docs/ml.devdocs.json +++ b/api_docs/ml.devdocs.json @@ -1596,7 +1596,7 @@ "label": "ML_PAGES", "description": [], "signature": [ - "{ readonly ANOMALY_DETECTION_JOBS_MANAGE: \"jobs\"; readonly ANOMALY_EXPLORER: \"explorer\"; readonly SINGLE_METRIC_VIEWER: \"timeseriesexplorer\"; readonly DATA_FRAME_ANALYTICS_JOBS_MANAGE: \"data_frame_analytics\"; readonly DATA_FRAME_ANALYTICS_SOURCE_SELECTION: \"data_frame_analytics/source_selection\"; readonly DATA_FRAME_ANALYTICS_CREATE_JOB: \"data_frame_analytics/new_job\"; readonly TRAINED_MODELS_MANAGE: \"trained_models\"; readonly TRAINED_MODELS_NODES: \"trained_models/nodes\"; readonly DATA_FRAME_ANALYTICS_EXPLORATION: \"data_frame_analytics/exploration\"; readonly DATA_FRAME_ANALYTICS_MAP: \"data_frame_analytics/map\"; readonly DATA_VISUALIZER: \"datavisualizer\"; readonly DATA_VISUALIZER_INDEX_SELECT: \"datavisualizer_index_select\"; readonly DATA_VISUALIZER_FILE: \"filedatavisualizer\"; readonly DATA_VISUALIZER_INDEX_VIEWER: \"jobs/new_job/datavisualizer\"; readonly ANOMALY_DETECTION_CREATE_JOB: \"jobs/new_job\"; readonly ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER: \"jobs/new_job/recognize\"; readonly ANOMALY_DETECTION_CREATE_JOB_ADVANCED: \"jobs/new_job/advanced\"; readonly ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE: \"jobs/new_job/step/job_type\"; readonly ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX: \"jobs/new_job/step/index_or_search\"; readonly ANOMALY_DETECTION_CREATE_JOB_FROM_LENS: \"jobs/new_job/from_lens\"; readonly SETTINGS: \"settings\"; readonly CALENDARS_MANAGE: \"settings/calendars_list\"; readonly CALENDARS_NEW: \"settings/calendars_list/new_calendar\"; readonly CALENDARS_EDIT: \"settings/calendars_list/edit_calendar\"; readonly FILTER_LISTS_MANAGE: \"settings/filter_lists\"; readonly FILTER_LISTS_NEW: \"settings/filter_lists/new_filter_list\"; readonly FILTER_LISTS_EDIT: \"settings/filter_lists/edit_filter_list\"; readonly ACCESS_DENIED: \"access-denied\"; readonly OVERVIEW: \"overview\"; readonly AIOPS: \"aiops\"; readonly AIOPS_EXPLAIN_LOG_RATE_SPIKES: \"aiops/explain_log_rate_spikes\"; readonly AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT: \"aiops/explain_log_rate_spikes_index_select\"; }" + "{ readonly ANOMALY_DETECTION_JOBS_MANAGE: \"jobs\"; readonly ANOMALY_EXPLORER: \"explorer\"; readonly SINGLE_METRIC_VIEWER: \"timeseriesexplorer\"; readonly DATA_FRAME_ANALYTICS_JOBS_MANAGE: \"data_frame_analytics\"; readonly DATA_FRAME_ANALYTICS_SOURCE_SELECTION: \"data_frame_analytics/source_selection\"; readonly DATA_FRAME_ANALYTICS_CREATE_JOB: \"data_frame_analytics/new_job\"; readonly TRAINED_MODELS_MANAGE: \"trained_models\"; readonly TRAINED_MODELS_NODES: \"trained_models/nodes\"; readonly DATA_FRAME_ANALYTICS_EXPLORATION: \"data_frame_analytics/exploration\"; readonly DATA_FRAME_ANALYTICS_MAP: \"data_frame_analytics/map\"; readonly DATA_VISUALIZER: \"datavisualizer\"; readonly DATA_VISUALIZER_INDEX_SELECT: \"datavisualizer_index_select\"; readonly DATA_VISUALIZER_FILE: \"filedatavisualizer\"; readonly DATA_VISUALIZER_INDEX_VIEWER: \"jobs/new_job/datavisualizer\"; readonly ANOMALY_DETECTION_CREATE_JOB: \"jobs/new_job\"; readonly ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER: \"jobs/new_job/recognize\"; readonly ANOMALY_DETECTION_CREATE_JOB_ADVANCED: \"jobs/new_job/advanced\"; readonly ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE: \"jobs/new_job/step/job_type\"; readonly ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX: \"jobs/new_job/step/index_or_search\"; readonly ANOMALY_DETECTION_CREATE_JOB_FROM_LENS: \"jobs/new_job/from_lens\"; readonly SETTINGS: \"settings\"; readonly CALENDARS_MANAGE: \"settings/calendars_list\"; readonly CALENDARS_NEW: \"settings/calendars_list/new_calendar\"; readonly CALENDARS_EDIT: \"settings/calendars_list/edit_calendar\"; readonly FILTER_LISTS_MANAGE: \"settings/filter_lists\"; readonly FILTER_LISTS_NEW: \"settings/filter_lists/new_filter_list\"; readonly FILTER_LISTS_EDIT: \"settings/filter_lists/edit_filter_list\"; readonly ACCESS_DENIED: \"access-denied\"; readonly OVERVIEW: \"overview\"; readonly NOTIFICATIONS: \"notifications\"; readonly AIOPS: \"aiops\"; readonly AIOPS_EXPLAIN_LOG_RATE_SPIKES: \"aiops/explain_log_rate_spikes\"; readonly AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT: \"aiops/explain_log_rate_spikes_index_select\"; readonly AIOPS_LOG_CATEGORIZATION: \"aiops/log_categorization\"; readonly AIOPS_LOG_CATEGORIZATION_INDEX_SELECT: \"aiops/log_categorization_index_select\"; }" ], "path": "x-pack/plugins/ml/common/constants/locator.ts", "deprecated": false, diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 85c2ac18cb50f..7a0d0fd31ca6a 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: 2022-09-15 +date: 2022-09-19 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 a1c0682748923..9ca08f60c1a31 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: 2022-09-15 +date: 2022-09-19 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 4207e2a1b1b94..afcda51989455 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: 2022-09-15 +date: 2022-09-19 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 c51681c2f0345..5689584f917a7 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: 2022-09-15 +date: 2022-09-19 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 7db96060f323c..066ad2b4b2fb0 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index ebeeaebf779e9..be4d5adadeff9 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -3068,6 +3068,60 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "observability", + "id": "def-public.NavigationSection", + "type": "Interface", + "tags": [], + "label": "NavigationSection", + "description": [], + "path": "x-pack/plugins/observability/public/services/navigation_registry.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-public.NavigationSection.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/observability/public/services/navigation_registry.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-public.NavigationSection.sortKey", + "type": "number", + "tags": [], + "label": "sortKey", + "description": [], + "path": "x-pack/plugins/observability/public/services/navigation_registry.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-public.NavigationSection.entries", + "type": "Array", + "tags": [], + "label": "entries", + "description": [], + "signature": [ + "NavigationEntry", + "[]" + ], + "path": "x-pack/plugins/observability/public/services/navigation_registry.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "observability", "id": "def-public.ObservabilityFetchDataResponse", @@ -5558,7 +5612,13 @@ " | undefined; list: () => string[]; }; navigation: { registerSections: (sections$: ", "Observable", "<", - "NavigationSection", + { + "pluginId": "observability", + "scope": "public", + "docId": "kibObservabilityPluginApi", + "section": "def-public.NavigationSection", + "text": "NavigationSection" + }, "[]>) => void; }; useRulesLink: (options?: ", "Options", ") => ", @@ -5791,7 +5851,7 @@ "label": "client", "description": [], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -5909,7 +5969,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", @@ -8013,180 +8073,1468 @@ "initialIsOpen": false } ], - "objects": [], - "setup": { - "parentPluginId": "observability", - "id": "def-server.ObservabilityPluginSetup", - "type": "Type", - "tags": [], - "label": "ObservabilityPluginSetup", - "description": [], - "signature": [ - "{ getScopedAnnotationsClient: (requestContext: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, - " & { licensing: Promise<", - { - "pluginId": "licensing", - "scope": "server", - "docId": "kibLicensingPluginApi", - "section": "def-server.LicensingApiRequestHandlerContext", - "text": "LicensingApiRequestHandlerContext" - }, - ">; }, request: ", - "KibanaRequest", - ") => Promise<{ readonly index: string; create: (createParams: { annotation: { type: string; }; '@timestamp': string; message: string; } & { tags?: string[] | undefined; service?: { name?: string | undefined; environment?: string | undefined; version?: string | undefined; } | undefined; }) => Promise<{ _id: string; _index: string; _source: ", - "Annotation", - "; }>; getById: (getByIdParams: { id: string; }) => Promise<", - "GetResponse", - ">; delete: (deleteParams: { id: string; }) => Promise<", - "WriteResponseBase", - ">; } | undefined>; }" - ], - "path": "x-pack/plugins/observability/server/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "lifecycle": "setup", - "initialIsOpen": true - } - }, - "common": { - "classes": [], - "functions": [ + "objects": [ { "parentPluginId": "observability", - "id": "def-common.formatDurationFromTimeUnitChar", - "type": "Function", + "id": "def-server.uiSettings", + "type": "Object", "tags": [], - "label": "formatDurationFromTimeUnitChar", - "description": [], - "signature": [ - "(time: number, unit: ", - { - "pluginId": "observability", - "scope": "common", - "docId": "kibObservabilityPluginApi", - "section": "def-common.TimeUnitChar", - "text": "TimeUnitChar" - }, - ") => string" + "label": "uiSettings", + "description": [ + "\nuiSettings definitions for Observability." ], - "path": "x-pack/plugins/observability/common/utils/formatters/duration.ts", + "path": "x-pack/plugins/observability/server/ui_settings.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "observability", - "id": "def-common.formatDurationFromTimeUnitChar.$1", - "type": "number", + "id": "def-server.uiSettings.enableNewSyntheticsView", + "type": "Object", "tags": [], - "label": "time", + "label": "[enableNewSyntheticsView]", "description": [], - "signature": [ - "number" - ], - "path": "x-pack/plugins/observability/common/utils/formatters/duration.ts", + "path": "x-pack/plugins/observability/server/ui_settings.ts", "deprecated": false, "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "observability", - "id": "def-common.formatDurationFromTimeUnitChar.$2", - "type": "CompoundType", - "tags": [], - "label": "unit", - "description": [], - "signature": [ + "children": [ { - "pluginId": "observability", - "scope": "common", - "docId": "kibObservabilityPluginApi", - "section": "def-common.TimeUnitChar", - "text": "TimeUnitChar" + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableNewSyntheticsView.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableNewSyntheticsView.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableNewSyntheticsView.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableNewSyntheticsView.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableNewSyntheticsView.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableNewSyntheticsView.requiresPageReload", + "type": "boolean", + "tags": [], + "label": "requiresPageReload", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false } - ], - "path": "x-pack/plugins/observability/common/utils/formatters/duration.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "observability", - "id": "def-common.getProbabilityFromProgressiveLoadingQuality", - "type": "Function", - "tags": [], - "label": "getProbabilityFromProgressiveLoadingQuality", - "description": [], - "signature": [ - "(quality: ", - { - "pluginId": "observability", - "scope": "common", - "docId": "kibObservabilityPluginApi", - "section": "def-common.ProgressiveLoadingQuality", - "text": "ProgressiveLoadingQuality" + ] }, - ") => number" - ], - "path": "x-pack/plugins/observability/common/progressive_loading.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "observability", - "id": "def-common.getProbabilityFromProgressiveLoadingQuality.$1", - "type": "Enum", + "id": "def-server.uiSettings.enableInspectEsQueries", + "type": "Object", "tags": [], - "label": "quality", + "label": "[enableInspectEsQueries]", "description": [], - "signature": [ - { - "pluginId": "observability", - "scope": "common", - "docId": "kibObservabilityPluginApi", - "section": "def-common.ProgressiveLoadingQuality", - "text": "ProgressiveLoadingQuality" - } - ], - "path": "x-pack/plugins/observability/common/progressive_loading.ts", + "path": "x-pack/plugins/observability/server/ui_settings.ts", "deprecated": false, "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - } - ], - "interfaces": [], - "enums": [ - { - "parentPluginId": "observability", - "id": "def-common.ProcessorEvent", - "type": "Enum", - "tags": [], - "label": "ProcessorEvent", - "description": [], - "path": "x-pack/plugins/observability/common/processor_event.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "observability", - "id": "def-common.ProgressiveLoadingQuality", - "type": "Enum", - "tags": [], + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableInspectEsQueries.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableInspectEsQueries.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableInspectEsQueries.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableInspectEsQueries.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableInspectEsQueries.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableInspectEsQueries.requiresPageReload", + "type": "boolean", + "tags": [], + "label": "requiresPageReload", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.maxSuggestions", + "type": "Object", + "tags": [], + "label": "[maxSuggestions]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.maxSuggestions.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.maxSuggestions.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.maxSuggestions.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.maxSuggestions.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.maxSuggestions.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableComparisonByDefault", + "type": "Object", + "tags": [], + "label": "[enableComparisonByDefault]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableComparisonByDefault.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableComparisonByDefault.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableComparisonByDefault.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableComparisonByDefault.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableComparisonByDefault.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.defaultApmServiceEnvironment", + "type": "Object", + "tags": [], + "label": "[defaultApmServiceEnvironment]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.defaultApmServiceEnvironment.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.defaultApmServiceEnvironment.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.defaultApmServiceEnvironment.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.defaultApmServiceEnvironment.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.defaultApmServiceEnvironment.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.defaultApmServiceEnvironment.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmProgressiveLoading", + "type": "Object", + "tags": [], + "label": "[apmProgressiveLoading]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmProgressiveLoading.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmProgressiveLoading.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmProgressiveLoading.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmProgressiveLoading.value", + "type": "Enum", + "tags": [], + "label": "value", + "description": [], + "signature": [ + { + "pluginId": "observability", + "scope": "common", + "docId": "kibObservabilityPluginApi", + "section": "def-common.ProgressiveLoadingQuality", + "text": "ProgressiveLoadingQuality" + } + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmProgressiveLoading.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "<", + { + "pluginId": "observability", + "scope": "common", + "docId": "kibObservabilityPluginApi", + "section": "def-common.ProgressiveLoadingQuality", + "text": "ProgressiveLoadingQuality" + }, + ">" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmProgressiveLoading.requiresPageReload", + "type": "boolean", + "tags": [], + "label": "requiresPageReload", + "description": [], + "signature": [ + "false" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmProgressiveLoading.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "\"select\"" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmProgressiveLoading.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + { + "pluginId": "observability", + "scope": "common", + "docId": "kibObservabilityPluginApi", + "section": "def-common.ProgressiveLoadingQuality", + "text": "ProgressiveLoadingQuality" + }, + "[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmProgressiveLoading.optionLabels", + "type": "Object", + "tags": [], + "label": "optionLabels", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmProgressiveLoading.optionLabels.ProgressiveLoadingQuality.off", + "type": "string", + "tags": [], + "label": "[ProgressiveLoadingQuality.off]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmProgressiveLoading.optionLabels.ProgressiveLoadingQuality.low", + "type": "string", + "tags": [], + "label": "[ProgressiveLoadingQuality.low]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmProgressiveLoading.optionLabels.ProgressiveLoadingQuality.medium", + "type": "string", + "tags": [], + "label": "[ProgressiveLoadingQuality.medium]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmProgressiveLoading.optionLabels.ProgressiveLoadingQuality.high", + "type": "string", + "tags": [], + "label": "[ProgressiveLoadingQuality.high]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceGroups", + "type": "Object", + "tags": [], + "label": "[enableServiceGroups]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceGroups.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceGroups.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceGroups.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceGroups.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceGroups.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceGroups.requiresPageReload", + "type": "boolean", + "tags": [], + "label": "requiresPageReload", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableServiceGroups.showInLabs", + "type": "boolean", + "tags": [], + "label": "showInLabs", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceInventoryOptimizedSorting", + "type": "Object", + "tags": [], + "label": "[apmServiceInventoryOptimizedSorting]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceInventoryOptimizedSorting.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceInventoryOptimizedSorting.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceInventoryOptimizedSorting.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceInventoryOptimizedSorting.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceInventoryOptimizedSorting.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceInventoryOptimizedSorting.requiresPageReload", + "type": "boolean", + "tags": [], + "label": "requiresPageReload", + "description": [], + "signature": [ + "false" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceInventoryOptimizedSorting.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "\"boolean\"" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceInventoryOptimizedSorting.showInLabs", + "type": "boolean", + "tags": [], + "label": "showInLabs", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceGroupMaxNumberOfServices", + "type": "Object", + "tags": [], + "label": "[apmServiceGroupMaxNumberOfServices]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceGroupMaxNumberOfServices.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceGroupMaxNumberOfServices.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceGroupMaxNumberOfServices.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceGroupMaxNumberOfServices.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmServiceGroupMaxNumberOfServices.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmTraceExplorerTab", + "type": "Object", + "tags": [], + "label": "[apmTraceExplorerTab]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmTraceExplorerTab.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmTraceExplorerTab.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmTraceExplorerTab.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmTraceExplorerTab.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmTraceExplorerTab.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmTraceExplorerTab.requiresPageReload", + "type": "boolean", + "tags": [], + "label": "requiresPageReload", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmTraceExplorerTab.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "\"boolean\"" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmTraceExplorerTab.showInLabs", + "type": "boolean", + "tags": [], + "label": "showInLabs", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmOperationsTab", + "type": "Object", + "tags": [], + "label": "[apmOperationsTab]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmOperationsTab.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmOperationsTab.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmOperationsTab.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmOperationsTab.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmOperationsTab.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmOperationsTab.requiresPageReload", + "type": "boolean", + "tags": [], + "label": "requiresPageReload", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmOperationsTab.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "\"boolean\"" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmOperationsTab.showInLabs", + "type": "boolean", + "tags": [], + "label": "showInLabs", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmLabsButton", + "type": "Object", + "tags": [], + "label": "[apmLabsButton]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmLabsButton.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmLabsButton.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmLabsButton.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmLabsButton.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmLabsButton.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmLabsButton.requiresPageReload", + "type": "boolean", + "tags": [], + "label": "requiresPageReload", + "description": [], + "signature": [ + "true" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.apmLabsButton.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "\"boolean\"" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "initialIsOpen": false + } + ], + "setup": { + "parentPluginId": "observability", + "id": "def-server.ObservabilityPluginSetup", + "type": "Type", + "tags": [], + "label": "ObservabilityPluginSetup", + "description": [], + "signature": [ + "{ getScopedAnnotationsClient: (requestContext: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " & { licensing: Promise<", + { + "pluginId": "licensing", + "scope": "server", + "docId": "kibLicensingPluginApi", + "section": "def-server.LicensingApiRequestHandlerContext", + "text": "LicensingApiRequestHandlerContext" + }, + ">; }, request: ", + "KibanaRequest", + ") => Promise<{ readonly index: string; create: (createParams: { annotation: { type: string; }; '@timestamp': string; message: string; } & { tags?: string[] | undefined; service?: { name?: string | undefined; environment?: string | undefined; version?: string | undefined; } | undefined; }) => Promise<{ _id: string; _index: string; _source: ", + "Annotation", + "; }>; getById: (getByIdParams: { id: string; }) => Promise<", + "GetResponse", + ">; delete: (deleteParams: { id: string; }) => Promise<", + "WriteResponseBase", + ">; } | undefined>; }" + ], + "path": "x-pack/plugins/observability/server/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "lifecycle": "setup", + "initialIsOpen": true + } + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "observability", + "id": "def-common.formatDurationFromTimeUnitChar", + "type": "Function", + "tags": [], + "label": "formatDurationFromTimeUnitChar", + "description": [], + "signature": [ + "(time: number, unit: ", + { + "pluginId": "observability", + "scope": "common", + "docId": "kibObservabilityPluginApi", + "section": "def-common.TimeUnitChar", + "text": "TimeUnitChar" + }, + ") => string" + ], + "path": "x-pack/plugins/observability/common/utils/formatters/duration.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-common.formatDurationFromTimeUnitChar.$1", + "type": "number", + "tags": [], + "label": "time", + "description": [], + "signature": [ + "number" + ], + "path": "x-pack/plugins/observability/common/utils/formatters/duration.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "observability", + "id": "def-common.formatDurationFromTimeUnitChar.$2", + "type": "CompoundType", + "tags": [], + "label": "unit", + "description": [], + "signature": [ + { + "pluginId": "observability", + "scope": "common", + "docId": "kibObservabilityPluginApi", + "section": "def-common.TimeUnitChar", + "text": "TimeUnitChar" + } + ], + "path": "x-pack/plugins/observability/common/utils/formatters/duration.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "observability", + "id": "def-common.getProbabilityFromProgressiveLoadingQuality", + "type": "Function", + "tags": [], + "label": "getProbabilityFromProgressiveLoadingQuality", + "description": [], + "signature": [ + "(quality: ", + { + "pluginId": "observability", + "scope": "common", + "docId": "kibObservabilityPluginApi", + "section": "def-common.ProgressiveLoadingQuality", + "text": "ProgressiveLoadingQuality" + }, + ") => number" + ], + "path": "x-pack/plugins/observability/common/progressive_loading.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-common.getProbabilityFromProgressiveLoadingQuality.$1", + "type": "Enum", + "tags": [], + "label": "quality", + "description": [], + "signature": [ + { + "pluginId": "observability", + "scope": "common", + "docId": "kibObservabilityPluginApi", + "section": "def-common.ProgressiveLoadingQuality", + "text": "ProgressiveLoadingQuality" + } + ], + "path": "x-pack/plugins/observability/common/progressive_loading.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [ + { + "parentPluginId": "observability", + "id": "def-common.ProcessorEvent", + "type": "Enum", + "tags": [], + "label": "ProcessorEvent", + "description": [], + "path": "x-pack/plugins/observability/common/processor_event.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "observability", + "id": "def-common.ProgressiveLoadingQuality", + "type": "Enum", + "tags": [], "label": "ProgressiveLoadingQuality", "description": [], "path": "x-pack/plugins/observability/common/progressive_loading.ts", @@ -8196,6 +9544,21 @@ } ], "misc": [ + { + "parentPluginId": "observability", + "id": "def-common.apmLabsButton", + "type": "string", + "tags": [], + "label": "apmLabsButton", + "description": [], + "signature": [ + "\"observability:apmLabsButton\"" + ], + "path": "x-pack/plugins/observability/common/ui_settings_keys.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observability", "id": "def-common.apmOperationsTab", @@ -8453,6 +9816,36 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "observability", + "id": "def-common.enableNewSyntheticsView", + "type": "string", + "tags": [], + "label": "enableNewSyntheticsView", + "description": [], + "signature": [ + "\"observability:enableNewSyntheticsView\"" + ], + "path": "x-pack/plugins/observability/common/ui_settings_keys.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "observability", + "id": "def-common.enableServiceGroups", + "type": "string", + "tags": [], + "label": "enableServiceGroups", + "description": [], + "signature": [ + "\"observability:enableServiceGroups\"" + ], + "path": "x-pack/plugins/observability/common/ui_settings_keys.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observability", "id": "def-common.maxSuggestions", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index e8520140a70ea..bb2bcdb3286d5 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: 2022-09-15 +date: 2022-09-19 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 | |-------------------|-----------|------------------------|-----------------| -| 397 | 2 | 394 | 30 | +| 501 | 2 | 497 | 30 | ## Client @@ -48,6 +48,9 @@ Contact [Observability UI](https://github.com/orgs/elastic/teams/observability-u ### Setup +### Objects + + ### Functions diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index b4dbdc99d43ff..a335b787eb7c6 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: 2022-09-15 +date: 2022-09-19 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 95f30da6410ab..d2deb071ca5da 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,27 +15,27 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 455 | 380 | 36 | +| 458 | 384 | 38 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 30837 | 180 | 20600 | 971 | +| 31125 | 182 | 20884 | 975 | ## Plugin Directory | Plugin name           | Maintaining team | Description | API Cnt | Any Cnt | Missing
comments | Missing
exports | |--------------|----------------|-----------|--------------|----------|---------------|--------| -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 273 | 0 | 268 | 19 | -| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 23 | 0 | 19 | 1 | -| | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 7 | 0 | 0 | 1 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 214 | 0 | 209 | 19 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 36 | 1 | 32 | 2 | +| | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 9 | 0 | 0 | 2 | | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 370 | 0 | 361 | 22 | | | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 38 | 0 | 38 | 52 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 9 | 0 | 9 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 80 | 1 | 71 | 2 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Canvas application to Kibana | 9 | 0 | 8 | 3 | -| | [ResponseOps](https://github.com/orgs/elastic/teams/response-ops) | The Case management system in Kibana | 84 | 0 | 68 | 29 | +| | [ResponseOps](https://github.com/orgs/elastic/teams/response-ops) | The Case management system in Kibana | 87 | 0 | 71 | 28 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | - | 264 | 2 | 249 | 9 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 29 | 0 | 24 | 0 | | | [Cloud Security Posture](https://github.com/orgs/elastic/teams/cloud-posture-security) | The cloud security posture plugin | 18 | 0 | 2 | 3 | @@ -53,12 +53,12 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 966 | 0 | 208 | 1 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | The Data Visualizer tools help you understand your data, by analyzing the metrics and fields in a log file or an existing Elasticsearch index. | 28 | 3 | 24 | 1 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 10 | 0 | 8 | 2 | -| | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 95 | 0 | 78 | 4 | +| | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 96 | 0 | 79 | 4 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 35 | 2 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds embeddables service to Kibana | 515 | 0 | 415 | 4 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | | | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 51 | 0 | 42 | 0 | -| | [Enterprise Search](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 8 | 0 | 8 | 0 | +| | [Enterprise Search](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 9 | 0 | 9 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 114 | 3 | 110 | 3 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | The Event Annotation service contains expressions for event annotations | 170 | 0 | 170 | 3 | | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 106 | 0 | 106 | 10 | @@ -80,12 +80,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Index pattern fields and ambiguous values formatters | 288 | 5 | 249 | 3 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 62 | 0 | 62 | 2 | | | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 240 | 0 | 6 | 2 | -| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 979 | 3 | 880 | 15 | +| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 969 | 3 | 870 | 17 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | globalSearchProviders | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | graph | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 0 | 0 | 0 | 0 | | grokdebugger | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | +| | [Journey Onboarding](https://github.com/orgs/elastic/teams/platform-onboarding) | Guided onboarding framework | 12 | 0 | 12 | 1 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 143 | 0 | 104 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 4 | 0 | 4 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 177 | 0 | 172 | 3 | @@ -99,7 +100,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | kibanaUsageCollection | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 0 | 0 | 0 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 615 | 3 | 418 | 9 | | | [Security Team](https://github.com/orgs/elastic/teams/security-team) | - | 3 | 0 | 3 | 1 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 641 | 0 | 552 | 41 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 639 | 0 | 552 | 41 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 8 | 0 | 8 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 3 | 0 | 3 | 0 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | @@ -113,10 +114,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Stack Monitoring](https://github.com/orgs/elastic/teams/stack-monitoring-ui) | - | 9 | 0 | 9 | 0 | | | [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 | -| | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 397 | 2 | 394 | 30 | +| | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 501 | 2 | 497 | 30 | | | [Security asset management](https://github.com/orgs/elastic/teams/security-asset-management) | - | 14 | 0 | 14 | 2 | | 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). | 243 | 2 | 187 | 12 | +| | [profiling](https://github.com/orgs/elastic/teams/profiling-ui) | - | 14 | 1 | 14 | 0 | | | [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 | @@ -131,13 +133,14 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 32 | 0 | 13 | 0 | | | [Kibana Reporting Services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Kibana Screenshotting Plugin | 27 | 0 | 8 | 4 | | searchprofiler | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | -| | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 245 | 0 | 90 | 0 | +| | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 247 | 0 | 90 | 0 | | | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 54 | 0 | 53 | 22 | | | [Security Team](https://github.com/orgs/elastic/teams/security-team) | - | 3 | 0 | 3 | 1 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds URL Service and sharing capabilities to Kibana | 114 | 0 | 55 | 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 | | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 4 | 0 | 4 | 0 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 2 | 0 | 2 | 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 | | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 83 | 0 | 41 | 7 | | | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 44 | 0 | 1 | 0 | @@ -151,7 +154,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 428 | 0 | 407 | 46 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds UI Actions service to Kibana | 132 | 0 | 91 | 11 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends UI Actions plugin with more functionality | 205 | 0 | 142 | 9 | -| | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list which can be integrated into apps | 54 | 0 | 52 | 2 | +| | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list which can be integrated into apps | 61 | 0 | 59 | 2 | | | [Unified Search](https://github.com/orgs/elastic/teams/kibana-app-services) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 122 | 2 | 96 | 16 | | upgradeAssistant | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | urlDrilldown | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds drilldown implementations to Kibana | 0 | 0 | 0 | 0 | @@ -171,7 +174,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Registers the vega visualization. Is the elastic version of vega and vega-lite libraries. | 2 | 0 | 2 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the vislib visualizations. These are the classical area/line/bar, pie, gauge/goal and heatmap charts. We want to replace them with elastic-charts. | 26 | 0 | 25 | 1 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the new xy-axis chart using the elastic-charts library, which will eventually replace the vislib xy-axis charts including bar, area, and line. | 53 | 0 | 50 | 5 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 419 | 12 | 390 | 15 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 611 | 12 | 582 | 14 | | watcher | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | ## Package Directory @@ -180,7 +183,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] |--------------|----------------|-----------|--------------|----------|---------------|--------| | | [Owner missing] | - | 11 | 5 | 11 | 0 | | | Machine Learning UI | React components for AIOps related efforts. | 6 | 0 | 6 | 0 | -| | Machine Learning UI | Static utilities for AIOps related efforts. | 49 | 0 | 24 | 0 | +| | Machine Learning UI | Static utilities for AIOps related efforts. | 51 | 0 | 26 | 0 | | | [Owner missing] | Alerts components and hooks | 9 | 1 | 9 | 0 | | | Kibana Core | Kibana Analytics tool | 73 | 0 | 73 | 2 | | | Kibana Core | - | 96 | 0 | 0 | 0 | @@ -189,7 +192,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 18 | 0 | 0 | 0 | | | Kibana Core | - | 20 | 0 | 0 | 0 | | | [Owner missing] | - | 16 | 0 | 16 | 0 | -| | [Owner missing] | Elastic APM trace data generator | 72 | 0 | 72 | 12 | +| | [Owner missing] | Elastic APM trace data generator | 74 | 0 | 74 | 13 | | | [Owner missing] | - | 11 | 0 | 11 | 0 | | | [Owner missing] | - | 10 | 0 | 10 | 0 | | | [Owner missing] | - | 76 | 0 | 76 | 0 | @@ -421,6 +424,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 2 | 0 | 2 | 0 | | | Operations | - | 254 | 5 | 213 | 11 | | | [Owner missing] | - | 135 | 8 | 103 | 2 | +| | [Owner missing] | - | 2 | 0 | 1 | 0 | | | [Owner missing] | - | 72 | 0 | 55 | 0 | | | [Owner missing] | - | 8 | 0 | 2 | 0 | | | [Owner missing] | - | 113 | 1 | 65 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 07f323620fdba..10e47b145b6a9 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.devdocs.json b/api_docs/profiling.devdocs.json new file mode 100644 index 0000000000000..1b5e38396b43d --- /dev/null +++ b/api_docs/profiling.devdocs.json @@ -0,0 +1,242 @@ +{ + "id": "profiling", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "profiling", + "id": "def-server.ProfilingPluginSetup", + "type": "Interface", + "tags": [], + "label": "ProfilingPluginSetup", + "description": [], + "path": "x-pack/plugins/profiling/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "profiling", + "id": "def-server.ProfilingPluginStart", + "type": "Interface", + "tags": [], + "label": "ProfilingPluginStart", + "description": [], + "path": "x-pack/plugins/profiling/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "profiling", + "id": "def-common.fromMapToRecord", + "type": "Function", + "tags": [], + "label": "fromMapToRecord", + "description": [], + "signature": [ + "(m: Map) => Record" + ], + "path": "x-pack/plugins/profiling/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "profiling", + "id": "def-common.fromMapToRecord.$1", + "type": "Object", + "tags": [], + "label": "m", + "description": [], + "signature": [ + "Map" + ], + "path": "x-pack/plugins/profiling/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "profiling", + "id": "def-common.getRoutePaths", + "type": "Function", + "tags": [], + "label": "getRoutePaths", + "description": [], + "signature": [ + "() => { TopN: string; TopNContainers: string; TopNDeployments: string; TopNFunctions: string; TopNHosts: string; TopNThreads: string; TopNTraces: string; Flamechart: string; FrameInformation: string; }" + ], + "path": "x-pack/plugins/profiling/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "profiling", + "id": "def-common.timeRangeFromRequest", + "type": "Function", + "tags": [], + "label": "timeRangeFromRequest", + "description": [], + "signature": [ + "(request: any) => [number, number]" + ], + "path": "x-pack/plugins/profiling/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "profiling", + "id": "def-common.timeRangeFromRequest.$1", + "type": "Any", + "tags": [], + "label": "request", + "description": [], + "signature": [ + "any" + ], + "path": "x-pack/plugins/profiling/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "profiling", + "id": "def-common.INDEX_EVENTS", + "type": "string", + "tags": [], + "label": "INDEX_EVENTS", + "description": [], + "signature": [ + "\"profiling-events-all\"" + ], + "path": "x-pack/plugins/profiling/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "profiling", + "id": "def-common.INDEX_EXECUTABLES", + "type": "string", + "tags": [], + "label": "INDEX_EXECUTABLES", + "description": [], + "signature": [ + "\"profiling-executables\"" + ], + "path": "x-pack/plugins/profiling/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "profiling", + "id": "def-common.INDEX_FRAMES", + "type": "string", + "tags": [], + "label": "INDEX_FRAMES", + "description": [], + "signature": [ + "\"profiling-stackframes\"" + ], + "path": "x-pack/plugins/profiling/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "profiling", + "id": "def-common.INDEX_TRACES", + "type": "string", + "tags": [], + "label": "INDEX_TRACES", + "description": [], + "signature": [ + "\"profiling-stacktraces\"" + ], + "path": "x-pack/plugins/profiling/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "profiling", + "id": "def-common.NOT_AVAILABLE_LABEL", + "type": "string", + "tags": [], + "label": "NOT_AVAILABLE_LABEL", + "description": [], + "path": "x-pack/plugins/profiling/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "profiling", + "id": "def-common.PLUGIN_ID", + "type": "string", + "tags": [], + "label": "PLUGIN_ID", + "description": [], + "signature": [ + "\"profiling\"" + ], + "path": "x-pack/plugins/profiling/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "profiling", + "id": "def-common.PLUGIN_NAME", + "type": "string", + "tags": [], + "label": "PLUGIN_NAME", + "description": [], + "signature": [ + "\"profiling\"" + ], + "path": "x-pack/plugins/profiling/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx new file mode 100644 index 0000000000000..6feb509ba7a56 --- /dev/null +++ b/api_docs/profiling.mdx @@ -0,0 +1,41 @@ +--- +#### +#### This 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: kibProfilingPluginApi +slug: /kibana-dev-docs/api/profiling +title: "profiling" +image: https://source.unsplash.com/400x175/?github +description: API docs for the profiling plugin +date: 2022-09-19 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] +--- +import profilingObj from './profiling.devdocs.json'; + + + +Contact [profiling](https://github.com/orgs/elastic/teams/profiling-ui) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 14 | 1 | 14 | 0 | + +## Server + +### Setup + + +### Start + + +## Common + +### Functions + + +### Consts, variables and types + + diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 5727eee5149c2..3cb4537c8c597 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: 2022-09-15 +date: 2022-09-19 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 bfb754f7261fb..a85f36646abc2 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: 2022-09-15 +date: 2022-09-19 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 59c395024b234..daacd093e41ed 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: 2022-09-15 +date: 2022-09-19 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 63cb7763de4a9..8984e36894ca5 100644 --- a/api_docs/rule_registry.devdocs.json +++ b/api_docs/rule_registry.devdocs.json @@ -1650,9 +1650,9 @@ "section": "def-common.WithoutReservedActionGroups", "text": "WithoutReservedActionGroups" }, - ">) => Promise; validate?: { params?: ", + ">) => Promise; name: string; validate?: { params?: ", "RuleTypeParamsValidator", - " | undefined; } | undefined; id: string; name: string; cancelAlertsOnRuleTimeout?: boolean | undefined; actionGroups: ", + " | undefined; } | undefined; id: string; cancelAlertsOnRuleTimeout?: boolean | undefined; actionGroups: ", { "pluginId": "alerting", "scope": "common", @@ -1911,7 +1911,7 @@ "\nID of the Kibana feature associated with the index.\nUsed by alerts-as-data RBAC.\n\nNote from @dhurley14\nThe purpose of the `feature` param is to force the user to update\nthe data structure which contains the mapping of consumers to alerts\nas data indices. The idea is it is typed such that it forces the\nuser to go to the code and modify it. At least until a better system\nis put in place or we move the alerts as data client out of rule registry.\n" ], "signature": [ - "\"logs\" | \"apm\" | \"uptime\" | \"siem\" | \"infrastructure\" | \"observability\"" + "\"observability\" | \"logs\" | \"apm\" | \"uptime\" | \"siem\" | \"infrastructure\"" ], "path": "x-pack/plugins/rule_registry/server/rule_data_plugin_service/index_options.ts", "deprecated": false, diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index a3a5dede156c4..e74b4d9a4761e 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 5ec876764901b..8afc5bfce8623 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: 2022-09-15 +date: 2022-09-19 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 f8113f2246abe..548b209cca4d7 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: 2022-09-15 +date: 2022-09-19 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 66bd77d205270..e5ee6465eeb50 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.devdocs.json b/api_docs/saved_objects_management.devdocs.json index 2cd75f6e2bb2f..3c4ff5fc143a3 100644 --- a/api_docs/saved_objects_management.devdocs.json +++ b/api_docs/saved_objects_management.devdocs.json @@ -294,7 +294,7 @@ "label": "euiColumn", "description": [], "signature": [ - "{ children?: React.ReactNode; onError?: React.ReactEventHandler | undefined; render?: ((value: any, record: ", + "{ children?: React.ReactNode; name: React.ReactNode; description?: string | undefined; onError?: React.ReactEventHandler | undefined; render?: ((value: any, record: ", { "pluginId": "savedObjectsManagement", "scope": "public", @@ -302,7 +302,7 @@ "section": "def-public.SavedObjectsManagementRecord", "text": "SavedObjectsManagementRecord" }, - ") => React.ReactNode) | undefined; hidden?: boolean | undefined; color?: string | undefined; id?: string | undefined; className?: string | undefined; title?: string | undefined; onChange?: React.FormEventHandler | undefined; onKeyDown?: React.KeyboardEventHandler | undefined; onClick?: React.MouseEventHandler | undefined; description?: string | undefined; security?: string | undefined; name: React.ReactNode; field: (string & {}) | keyof ", + ") => React.ReactNode) | undefined; hidden?: boolean | undefined; color?: string | undefined; id?: string | undefined; className?: string | undefined; title?: string | undefined; onChange?: React.FormEventHandler | undefined; onKeyDown?: React.KeyboardEventHandler | undefined; onClick?: React.MouseEventHandler | undefined; security?: string | undefined; field: (string & {}) | keyof ", { "pluginId": "savedObjectsManagement", "scope": "public", diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 57ca08be0a5d3..811eda25dc426 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.devdocs.json b/api_docs/saved_objects_tagging.devdocs.json index 7b3c3512e7e03..c2f9424248bc6 100644 --- a/api_docs/saved_objects_tagging.devdocs.json +++ b/api_docs/saved_objects_tagging.devdocs.json @@ -1242,7 +1242,7 @@ "label": "errors", "description": [], "signature": [ - "{ color?: string | undefined; id?: string | undefined; description?: string | undefined; name?: string | undefined; }" + "{ name?: string | undefined; description?: string | undefined; color?: string | undefined; id?: string | undefined; }" ], "path": "x-pack/plugins/saved_objects_tagging/common/validation.ts", "deprecated": false, diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index d55c46efc1e8d..a8774a99de855 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: 2022-09-15 +date: 2022-09-19 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 5f906930e81d8..136082f983427 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index ba458bf7c8371..f77a033e025a5 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index a55c66d6c52c7..26f13dff94fb6 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index 2adfa62df7cd2..cc7404d88949f 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.devdocs.json b/api_docs/security.devdocs.json index 4dc4b7e085144..d400f91576d7d 100644 --- a/api_docs/security.devdocs.json +++ b/api_docs/security.devdocs.json @@ -522,6 +522,19 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "security", + "id": "def-public.SecurityLicenseFeatures.allowUserProfileCollaboration", + "type": "boolean", + "tags": [], + "label": "allowUserProfileCollaboration", + "description": [ + "\nIndicates whether we allow user profile collaboration features (suggest and privileges checks APIs)." + ], + "path": "x-pack/plugins/security/common/licensing/license_features.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "security", "id": "def-public.SecurityLicenseFeatures.layout", @@ -3966,6 +3979,19 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "security", + "id": "def-common.SecurityLicenseFeatures.allowUserProfileCollaboration", + "type": "boolean", + "tags": [], + "label": "allowUserProfileCollaboration", + "description": [ + "\nIndicates whether we allow user profile collaboration features (suggest and privileges checks APIs)." + ], + "path": "x-pack/plugins/security/common/licensing/license_features.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "security", "id": "def-common.SecurityLicenseFeatures.layout", diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 057465fcdb47c..58739f9635858 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Platform Security](https://github.com/orgs/elastic/teams/kibana-securit | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 245 | 0 | 90 | 0 | +| 247 | 0 | 90 | 0 | ## Client diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index f6e7dcb001687..5827c20db016b 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -64,7 +64,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly disableIsolationUIPendingStatuses: boolean; readonly riskyHostsEnabled: boolean; readonly riskyUsersEnabled: boolean; readonly pendingActionResponsesWithAck: boolean; readonly policyListEnabled: boolean; readonly policyResponseInFleetEnabled: boolean; readonly threatIntelligenceEnabled: boolean; readonly entityAnalyticsDashboardEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly responseActionsConsoleEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly disableIsolationUIPendingStatuses: boolean; readonly pendingActionResponsesWithAck: boolean; readonly policyListEnabled: boolean; readonly policyResponseInFleetEnabled: boolean; readonly threatIntelligenceEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly responseActionsConsoleEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/public/plugin.tsx", "deprecated": false, @@ -1095,7 +1095,7 @@ "label": "ConfigType", "description": [], "signature": [ - "Readonly<{} & { signalsIndex: string; maxRuleImportExportSize: number; maxRuleImportPayloadBytes: number; maxTimelineImportExportSize: number; maxTimelineImportPayloadBytes: number; alertMergeStrategy: \"allFields\" | \"missingFields\" | \"noFields\"; alertIgnoreFields: string[]; enableExperimental: string[]; packagerTaskInterval: string; prebuiltRulesFromFileSystem: boolean; prebuiltRulesFromSavedObjects: boolean; }> & { experimentalFeatures: Readonly<{ tGridEnabled: boolean; tGridEventRenderedViewEnabled: boolean; excludePoliciesInFilterEnabled: boolean; kubernetesEnabled: boolean; disableIsolationUIPendingStatuses: boolean; riskyHostsEnabled: boolean; riskyUsersEnabled: boolean; pendingActionResponsesWithAck: boolean; policyListEnabled: boolean; policyResponseInFleetEnabled: boolean; threatIntelligenceEnabled: boolean; entityAnalyticsDashboardEnabled: boolean; previewTelemetryUrlEnabled: boolean; responseActionsConsoleEnabled: boolean; insightsRelatedAlertsByProcessAncestry: boolean; extendedRuleExecutionLoggingEnabled: boolean; socTrendsEnabled: boolean; }>; }" + "Readonly<{} & { signalsIndex: string; maxRuleImportExportSize: number; maxRuleImportPayloadBytes: number; maxTimelineImportExportSize: number; maxTimelineImportPayloadBytes: number; alertMergeStrategy: \"allFields\" | \"missingFields\" | \"noFields\"; alertIgnoreFields: string[]; enableExperimental: string[]; packagerTaskInterval: string; prebuiltRulesFromFileSystem: boolean; prebuiltRulesFromSavedObjects: boolean; }> & { experimentalFeatures: Readonly<{ tGridEnabled: boolean; tGridEventRenderedViewEnabled: boolean; excludePoliciesInFilterEnabled: boolean; kubernetesEnabled: boolean; disableIsolationUIPendingStatuses: boolean; pendingActionResponsesWithAck: boolean; policyListEnabled: boolean; policyResponseInFleetEnabled: boolean; threatIntelligenceEnabled: boolean; previewTelemetryUrlEnabled: boolean; responseActionsConsoleEnabled: boolean; insightsRelatedAlertsByProcessAncestry: boolean; extendedRuleExecutionLoggingEnabled: boolean; socTrendsEnabled: boolean; }>; }" ], "path": "x-pack/plugins/security_solution/server/config.ts", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index e9529b9419ba3..cffbb73835c6a 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index c51dfb81f2beb..2e2273c7c835d 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index 6b02a6aee7909..015dc2f286af7 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 768b98bcf1466..b93d20e96b60f 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index d0f9561baa6a3..4d1caa1e75435 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 07ea237b98f6d..cfe1cb1abbca1 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.devdocs.json b/api_docs/stack_connectors.devdocs.json new file mode 100644 index 0000000000000..51e0fbc4412e4 --- /dev/null +++ b/api_docs/stack_connectors.devdocs.json @@ -0,0 +1,56 @@ +{ + "id": "stackConnectors", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [ + { + "parentPluginId": "stackConnectors", + "id": "def-common.AdditionalEmailServices", + "type": "Enum", + "tags": [], + "label": "AdditionalEmailServices", + "description": [], + "path": "x-pack/plugins/stack_connectors/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "misc": [ + { + "parentPluginId": "stackConnectors", + "id": "def-common.INTERNAL_BASE_STACK_CONNECTORS_API_PATH", + "type": "string", + "tags": [], + "label": "INTERNAL_BASE_STACK_CONNECTORS_API_PATH", + "description": [], + "signature": [ + "\"/internal/stack_connectors\"" + ], + "path": "x-pack/plugins/stack_connectors/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx new file mode 100644 index 0000000000000..dcb14bb1fa146 --- /dev/null +++ b/api_docs/stack_connectors.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: kibStackConnectorsPluginApi +slug: /kibana-dev-docs/api/stackConnectors +title: "stackConnectors" +image: https://source.unsplash.com/400x175/?github +description: API docs for the stackConnectors plugin +date: 2022-09-19 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] +--- +import stackConnectorsObj from './stack_connectors.devdocs.json'; + + + +Contact [Response Ops](https://github.com/orgs/elastic/teams/response-ops) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 2 | 0 | 2 | 0 | + +## Common + +### Enums + + +### Consts, variables and types + + diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index c3266964d5850..4760c73769ea6 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 19ac8779c574e..ae4f8095f6dd0 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.devdocs.json b/api_docs/telemetry_collection_manager.devdocs.json index 46639d54fba68..f5e1d86713cd2 100644 --- a/api_docs/telemetry_collection_manager.devdocs.json +++ b/api_docs/telemetry_collection_manager.devdocs.json @@ -76,7 +76,7 @@ "label": "esClient", "description": [], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -194,7 +194,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 525c730b4b2f5..206c000c79c51 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index 8236d204b52ee..c87118b1a050c 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 3898a9d769230..7f9fc5ae819a2 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 0dc87e4b317d9..a7be907819ce0 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.devdocs.json b/api_docs/timelines.devdocs.json index 49bd26f376c08..c5c0d5324560b 100644 --- a/api_docs/timelines.devdocs.json +++ b/api_docs/timelines.devdocs.json @@ -822,7 +822,7 @@ "EuiDataGridColumn", ", \"id\" | \"display\" | \"displayAsText\" | \"initialWidth\"> & Pick<", "EuiDataGridColumn", - ", \"id\" | \"schema\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", + ", \"schema\" | \"id\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", "TGridCellAction", "[] | undefined; category?: string | undefined; columnHeaderType: ", { @@ -856,7 +856,7 @@ "EuiDataGridColumn", ", \"id\" | \"display\" | \"displayAsText\" | \"initialWidth\"> & Pick<", "EuiDataGridColumn", - ", \"id\" | \"schema\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", + ", \"schema\" | \"id\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", "TGridCellAction", "[] | undefined; category?: string | undefined; columnHeaderType: ", { @@ -2559,7 +2559,7 @@ "EuiDataGridColumn", ", \"id\" | \"display\" | \"displayAsText\" | \"initialWidth\"> & Pick<", "EuiDataGridColumn", - ", \"id\" | \"schema\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", + ", \"schema\" | \"id\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", "TGridCellAction", "[] | undefined; category?: string | undefined; columnHeaderType: ", { @@ -3145,7 +3145,7 @@ "EuiDataGridColumn", ", \"id\" | \"display\" | \"displayAsText\" | \"initialWidth\"> & Pick<", "EuiDataGridColumn", - ", \"id\" | \"schema\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", + ", \"schema\" | \"id\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", "TGridCellAction", "[] | undefined; category?: string | undefined; columnHeaderType: ", { @@ -3173,7 +3173,7 @@ "EuiDataGridColumn", ", \"id\" | \"display\" | \"displayAsText\" | \"initialWidth\"> & Pick<", "EuiDataGridColumn", - ", \"id\" | \"schema\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", + ", \"schema\" | \"id\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", "TGridCellAction", "[] | undefined; category?: string | undefined; columnHeaderType: ", { @@ -4347,7 +4347,7 @@ "signature": [ "Pick<", "EuiDataGridColumn", - ", \"id\" | \"schema\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", + ", \"schema\" | \"id\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", "TGridCellAction", "[] | undefined; category?: string | undefined; columnHeaderType: ", { @@ -7075,7 +7075,7 @@ "signature": [ "Pick<", "EuiDataGridColumn", - ", \"id\" | \"schema\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", + ", \"schema\" | \"id\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", "TGridCellAction", "[] | undefined; category?: string | undefined; columnHeaderType: ", { @@ -7151,7 +7151,7 @@ "label": "DataProvidersAnd", "description": [], "signature": [ - "{ type?: ", + "{ name: string; type?: ", { "pluginId": "timelines", "scope": "common", @@ -7159,7 +7159,7 @@ "section": "def-common.DataProviderType", "text": "DataProviderType" }, - " | undefined; id: string; name: string; enabled: boolean; excluded: boolean; kqlQuery: string; queryMatch: ", + " | undefined; id: string; enabled: boolean; excluded: boolean; kqlQuery: string; queryMatch: ", { "pluginId": "timelines", "scope": "common", diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 367904d3c3073..a56ad276b24ae 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: 2022-09-15 +date: 2022-09-19 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 573d6175a741d..b13bda50369f9 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: 2022-09-15 +date: 2022-09-19 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 cdea7649565a3..77ded4f84172b 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -3466,7 +3466,7 @@ "section": "def-common.RuleType", "text": "RuleType" }, - ", \"id\" | \"name\" | \"actionGroups\" | \"defaultActionGroupId\" | \"recoveryActionGroup\" | \"producer\" | \"minimumLicenseRequired\" | \"defaultScheduleInterval\" | \"ruleTaskTimeout\" | \"doesSetRecoveryContext\">" + ", \"name\" | \"id\" | \"actionGroups\" | \"defaultActionGroupId\" | \"recoveryActionGroup\" | \"producer\" | \"minimumLicenseRequired\" | \"defaultScheduleInterval\" | \"ruleTaskTimeout\" | \"doesSetRecoveryContext\">" ], "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, @@ -3830,7 +3830,7 @@ "label": "setRuleProperty", "description": [], "signature": [ - "(key: Prop, value: ", + "(key: Prop, value: ", "SanitizedRule", "[Prop] | null) => void" ], diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 8dcad8be104e9..7e4fffb99a651 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 8f440da5d567d..9a66d5e877250 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: 2022-09-15 +date: 2022-09-19 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 bc0c192cc84e0..35aa624102ffb 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.devdocs.json b/api_docs/unified_field_list.devdocs.json index e7555c2ac5a6c..9ade69bc0eab0 100644 --- a/api_docs/unified_field_list.devdocs.json +++ b/api_docs/unified_field_list.devdocs.json @@ -284,6 +284,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldStatsProps.color", + "type": "string", + "tags": [], + "label": "color", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "unifiedFieldList", "id": "def-public.FieldStatsProps.datatestsubj", @@ -306,7 +320,7 @@ "label": "overrideMissingContent", "description": [], "signature": [ - "((params?: { noDataFound?: boolean | undefined; } | undefined) => JSX.Element | null) | undefined" + "((params: { element: JSX.Element; noDataFound?: boolean | undefined; }) => JSX.Element | null) | undefined" ], "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", "deprecated": false, @@ -323,6 +337,20 @@ "deprecated": false, "trackAdoption": false, "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldStatsProps.overrideMissingContent.$1.element", + "type": "Object", + "tags": [], + "label": "element", + "description": [], + "signature": [ + "JSX.Element" + ], + "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "unifiedFieldList", "id": "def-public.FieldStatsProps.overrideMissingContent.$1.noDataFound", @@ -413,6 +441,27 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldStatsProps.onAddFilter", + "type": "Function", + "tags": [], + "label": "onAddFilter", + "description": [], + "signature": [ + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.AddFieldFilterHandler", + "text": "AddFieldFilterHandler" + }, + " | undefined" + ], + "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -722,7 +771,82 @@ } ], "enums": [], - "misc": [], + "misc": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.AddFieldFilterHandler", + "type": "Type", + "tags": [], + "label": "AddFieldFilterHandler", + "description": [], + "signature": [ + "(field: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewField", + "text": "DataViewField" + }, + ", value: unknown, type: \"+\" | \"-\") => void" + ], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.AddFieldFilterHandler.$1", + "type": "Object", + "tags": [], + "label": "field", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewField", + "text": "DataViewField" + } + ], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.AddFieldFilterHandler.$2", + "type": "Unknown", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "unknown" + ], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.AddFieldFilterHandler.$3", + "type": "CompoundType", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "\"+\" | \"-\"" + ], + "path": "src/plugins/unified_field_list/public/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], "objects": [], "setup": { "parentPluginId": "unifiedFieldList", diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index 7b8485881de26..204d946fd0e2f 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-disco | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 54 | 0 | 52 | 2 | +| 61 | 0 | 59 | 2 | ## Client @@ -37,6 +37,9 @@ Contact [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-disco ### Interfaces +### Consts, variables and types + + ## Server ### Setup diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 48b056de3c9a8..fd8b1dd43f3f4 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: 2022-09-15 +date: 2022-09-19 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 7b806c7403134..910f357fd4217 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: 2022-09-15 +date: 2022-09-19 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 0f286636bf558..944cd04679c36 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.devdocs.json b/api_docs/usage_collection.devdocs.json index 7d580cf4289f8..c90dbcddab203 100644 --- a/api_docs/usage_collection.devdocs.json +++ b/api_docs/usage_collection.devdocs.json @@ -345,7 +345,7 @@ "\nRequest-scoped Elasticsearch client" ], "signature": [ - "{ get: { (this: That, params: ", + "{ name: string | symbol; get: { (this: That, params: ", "GetRequest", " | ", "GetRequest", @@ -463,7 +463,7 @@ "default", "; security: ", "default", - "; name: string | symbol; index: { (this: That, params: ", + "; index: { (this: That, params: ", "IndexRequest", " | ", "IndexRequest", diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 64a08ca43b97a..e8e459528f17a 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: 2022-09-15 +date: 2022-09-19 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 52f1bc99e8120..bbaddcb945c01 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: 2022-09-15 +date: 2022-09-19 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 3a0089cbce613..a818f4855f16e 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: 2022-09-15 +date: 2022-09-19 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 d663081d2bfca..85f000be9bc0f 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: 2022-09-15 +date: 2022-09-19 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 6a93f9f2cdca6..5f0cfaae3a14c 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: 2022-09-15 +date: 2022-09-19 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 394a056bbf185..4f2deb0511886 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: 2022-09-15 +date: 2022-09-19 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 ed2aea50a0cb5..d28039b1456fd 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: 2022-09-15 +date: 2022-09-19 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 d8ea231869dc7..c1a6e6872720b 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: 2022-09-15 +date: 2022-09-19 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 d03dda67b8499..e1763f4d1fede 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: 2022-09-15 +date: 2022-09-19 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 5829a0b885f35..e5d74082cbe79 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.devdocs.json b/api_docs/vis_type_vislib.devdocs.json index 22b92af62d579..2ba5f096eecd3 100644 --- a/api_docs/vis_type_vislib.devdocs.json +++ b/api_docs/vis_type_vislib.devdocs.json @@ -40,7 +40,7 @@ "label": "type", "description": [], "signature": [ - "\"goal\" | \"metric\" | \"gauge\" | \"heatmap\" | \"pie\"" + "\"metric\" | \"goal\" | \"gauge\" | \"heatmap\" | \"pie\"" ], "path": "src/plugins/vis_types/vislib/public/types.ts", "deprecated": false, @@ -359,7 +359,7 @@ "label": "VislibChartType", "description": [], "signature": [ - "\"goal\" | \"metric\" | \"gauge\" | \"heatmap\" | \"pie\"" + "\"metric\" | \"goal\" | \"gauge\" | \"heatmap\" | \"pie\"" ], "path": "src/plugins/vis_types/vislib/public/types.ts", "deprecated": false, diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 55dd3c6370f54..bc87e33e2fc83 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.devdocs.json b/api_docs/vis_type_xy.devdocs.json index 854b8f143a301..5468fa97bdb7a 100644 --- a/api_docs/vis_type_xy.devdocs.json +++ b/api_docs/vis_type_xy.devdocs.json @@ -831,7 +831,13 @@ "text": "SchemaConfig" }, ", \"params\"> & { params: {} | ", - "DateHistogramParams", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.DateHistogramParams", + "text": "DateHistogramParams" + }, " | ", "HistogramParams", " | ", diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 91a7729b5fb46..56b776e420bfb 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.devdocs.json b/api_docs/visualizations.devdocs.json index 352226b8a79ff..33b99385c989a 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -116,12 +116,20 @@ " | undefined) => Promise<", { "pluginId": "visualizations", - "scope": "public", + "scope": "common", "docId": "kibVisualizationsPluginApi", - "section": "def-public.NavigateToLensContext", + "section": "def-common.NavigateToLensContext", "text": "NavigateToLensContext" }, - " | null> | undefined) | undefined" + "<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.XYConfiguration", + "text": "XYConfiguration" + }, + "> | null> | undefined) | undefined" ], "path": "src/plugins/visualizations/public/vis_types/base_vis_type.ts", "deprecated": false, @@ -3006,67 +3014,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "visualizations", - "id": "def-public.NavigateToLensContext", - "type": "Interface", - "tags": [], - "label": "NavigateToLensContext", - "description": [], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "visualizations", - "id": "def-public.NavigateToLensContext.layers", - "type": "Object", - "tags": [], - "label": "layers", - "description": [], - "signature": [ - "{ [key: string]: ", - { - "pluginId": "visualizations", - "scope": "public", - "docId": "kibVisualizationsPluginApi", - "section": "def-public.VisualizeEditorLayersContext", - "text": "VisualizeEditorLayersContext" - }, - "; }" - ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.NavigateToLensContext.type", - "type": "string", - "tags": [], - "label": "type", - "description": [], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.NavigateToLensContext.configuration", - "type": "Object", - "tags": [], - "label": "configuration", - "description": [], - "signature": [ - "{ fill: string | number; legend: { isVisible: boolean; position: string; shouldTruncate: boolean; maxLines: number; showSingleSeries: boolean; }; gridLinesVisibility: { x: boolean; yLeft: boolean; yRight: boolean; }; tickLabelsVisibility?: { x: boolean; yLeft: boolean; yRight: boolean; } | undefined; axisTitlesVisibility?: { x: boolean; yLeft: boolean; yRight: boolean; } | undefined; valueLabels?: boolean | undefined; extents?: { yLeftExtent: AxisExtents; yRightExtent: AxisExtents; } | undefined; }" - ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "visualizations", "id": "def-public.Schema", @@ -4596,12 +4543,20 @@ " | undefined) => Promise<", { "pluginId": "visualizations", - "scope": "public", + "scope": "common", "docId": "kibVisualizationsPluginApi", - "section": "def-public.NavigateToLensContext", + "section": "def-common.NavigateToLensContext", "text": "NavigateToLensContext" }, - " | null> | undefined) | undefined" + "<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.XYConfiguration", + "text": "XYConfiguration" + }, + "> | null> | undefined) | undefined" ], "path": "src/plugins/visualizations/public/vis_types/types.ts", "deprecated": false, @@ -5474,430 +5429,166 @@ }, { "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext", + "id": "def-public.VisualizeInput", "type": "Interface", "tags": [], - "label": "VisualizeEditorLayersContext", + "label": "VisualizeInput", "description": [], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.indexPatternId", - "type": "string", - "tags": [], - "label": "indexPatternId", - "description": [], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.splitWithDateHistogram", - "type": "CompoundType", - "tags": [], - "label": "splitWithDateHistogram", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.xFieldName", - "type": "string", - "tags": [], - "label": "xFieldName", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.xMode", - "type": "string", - "tags": [], - "label": "xMode", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false - }, + "signature": [ { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.chartType", - "type": "string", - "tags": [], - "label": "chartType", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "visualizations", + "scope": "public", + "docId": "kibVisualizationsPluginApi", + "section": "def-public.VisualizeInput", + "text": "VisualizeInput" }, + " extends ", { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.axisPosition", - "type": "string", - "tags": [], - "label": "axisPosition", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false - }, + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.EmbeddableInput", + "text": "EmbeddableInput" + } + ], + "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.termsParams", + "id": "def-public.VisualizeInput.vis", "type": "Object", "tags": [], - "label": "termsParams", - "description": [], - "signature": [ - "Record | undefined" - ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.splitFields", - "type": "Array", - "tags": [], - "label": "splitFields", + "label": "vis", "description": [], "signature": [ - "string[] | undefined" + "{ colors?: { [key: string]: string; } | undefined; } | undefined" ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", + "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.splitMode", - "type": "string", + "id": "def-public.VisualizeInput.savedVis", + "type": "Object", "tags": [], - "label": "splitMode", + "label": "savedVis", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.SerializedVis", + "text": "SerializedVis" + }, + "<", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.VisParams", + "text": "VisParams" + }, + "> | undefined" ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", + "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.collapseFn", - "type": "string", + "id": "def-public.VisualizeInput.renderMode", + "type": "CompoundType", "tags": [], - "label": "collapseFn", + "label": "renderMode", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.RenderMode", + "text": "RenderMode" + }, + " | undefined" ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", + "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.splitFilters", - "type": "Array", + "id": "def-public.VisualizeInput.table", + "type": "Unknown", "tags": [], - "label": "splitFilters", + "label": "table", "description": [], "signature": [ - "SplitByFilters[] | undefined" + "unknown" ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", + "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.palette", + "id": "def-public.VisualizeInput.query", "type": "Object", "tags": [], - "label": "palette", + "label": "query", "description": [], "signature": [ - "PaletteOutput", - "<{ [key: string]: unknown; }> | undefined" + "Query", + " | undefined" ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", + "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.metrics", + "id": "def-public.VisualizeInput.filters", "type": "Array", "tags": [], - "label": "metrics", + "label": "filters", "description": [], "signature": [ - "VisualizeEditorMetricContext[]" + "Filter", + "[] | undefined" ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", + "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.timeInterval", - "type": "string", + "id": "def-public.VisualizeInput.timeRange", + "type": "Object", "tags": [], - "label": "timeInterval", + "label": "timeRange", "description": [], "signature": [ - "string | undefined" + "TimeRange", + " | undefined" ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", + "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.format", - "type": "string", + "id": "def-public.VisualizeInput.timeslice", + "type": "Object", "tags": [], - "label": "format", + "label": "timeslice", "description": [], "signature": [ - "string | undefined" + "[number, number] | undefined" ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.label", - "type": "string", - "tags": [], - "label": "label", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.layerId", - "type": "string", - "tags": [], - "label": "layerId", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeEditorLayersContext.dropPartialBuckets", - "type": "CompoundType", - "tags": [], - "label": "dropPartialBuckets", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/visualizations/public/vis_types/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeInput", - "type": "Interface", - "tags": [], - "label": "VisualizeInput", - "description": [], - "signature": [ - { - "pluginId": "visualizations", - "scope": "public", - "docId": "kibVisualizationsPluginApi", - "section": "def-public.VisualizeInput", - "text": "VisualizeInput" - }, - " extends ", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - } - ], - "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeInput.vis", - "type": "Object", - "tags": [], - "label": "vis", - "description": [], - "signature": [ - "{ colors?: { [key: string]: string; } | undefined; } | undefined" - ], - "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeInput.savedVis", - "type": "Object", - "tags": [], - "label": "savedVis", - "description": [], - "signature": [ - { - "pluginId": "visualizations", - "scope": "common", - "docId": "kibVisualizationsPluginApi", - "section": "def-common.SerializedVis", - "text": "SerializedVis" - }, - "<", - { - "pluginId": "visualizations", - "scope": "common", - "docId": "kibVisualizationsPluginApi", - "section": "def-common.VisParams", - "text": "VisParams" - }, - "> | undefined" - ], - "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeInput.renderMode", - "type": "CompoundType", - "tags": [], - "label": "renderMode", - "description": [], - "signature": [ - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.RenderMode", - "text": "RenderMode" - }, - " | undefined" - ], - "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeInput.table", - "type": "Unknown", - "tags": [], - "label": "table", - "description": [], - "signature": [ - "unknown" - ], - "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeInput.query", - "type": "Object", - "tags": [], - "label": "query", - "description": [], - "signature": [ - "Query", - " | undefined" - ], - "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeInput.filters", - "type": "Array", - "tags": [], - "label": "filters", - "description": [], - "signature": [ - "Filter", - "[] | undefined" - ], - "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeInput.timeRange", - "type": "Object", - "tags": [], - "label": "timeRange", - "description": [], - "signature": [ - "TimeRange", - " | undefined" - ], - "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeInput.timeslice", - "type": "Object", - "tags": [], - "label": "timeslice", - "description": [], - "signature": [ - "[number, number] | undefined" - ], - "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", + "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", "deprecated": false, "trackAdoption": false } @@ -6016,7 +5707,13 @@ "description": [], "signature": [ "{ type: \"xy_dimension\"; } & { label: string; aggType: string; params: {} | ", - "DateHistogramParams", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.DateHistogramParams", + "text": "DateHistogramParams" + }, " | ", "HistogramParams", " | ", @@ -7805,120 +7502,115 @@ }, { "parentPluginId": "visualizations", - "id": "def-common.SerializedVis", + "id": "def-common.AxesSettingsConfig", "type": "Interface", "tags": [], - "label": "SerializedVis", + "label": "AxesSettingsConfig", "description": [], - "signature": [ - { - "pluginId": "visualizations", - "scope": "common", - "docId": "kibVisualizationsPluginApi", - "section": "def-common.SerializedVis", - "text": "SerializedVis" - }, - "" - ], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "visualizations", - "id": "def-common.SerializedVis.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/visualizations/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "visualizations", - "id": "def-common.SerializedVis.title", - "type": "string", + "id": "def-common.AxesSettingsConfig.x", + "type": "boolean", "tags": [], - "label": "title", + "label": "x", "description": [], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-common.SerializedVis.description", - "type": "string", + "id": "def-common.AxesSettingsConfig.yRight", + "type": "boolean", "tags": [], - "label": "description", + "label": "yRight", "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-common.SerializedVis.type", - "type": "string", + "id": "def-common.AxesSettingsConfig.yLeft", + "type": "boolean", "tags": [], - "label": "type", + "label": "yLeft", "description": [], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.AxisExtentConfig", + "type": "Interface", + "tags": [], + "label": "AxisExtentConfig", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.AxisExtentConfig.mode", + "type": "CompoundType", + "tags": [], + "label": "mode", + "description": [], + "signature": [ + "\"custom\" | \"full\" | \"dataBounds\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-common.SerializedVis.params", - "type": "Uncategorized", + "id": "def-common.AxisExtentConfig.lowerBound", + "type": "number", "tags": [], - "label": "params", + "label": "lowerBound", "description": [], "signature": [ - "T" + "number | undefined" ], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-common.SerializedVis.uiState", - "type": "Any", + "id": "def-common.AxisExtentConfig.upperBound", + "type": "number", "tags": [], - "label": "uiState", + "label": "upperBound", "description": [], "signature": [ - "any" + "number | undefined" ], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-common.SerializedVis.data", - "type": "Object", + "id": "def-common.AxisExtentConfig.enforce", + "type": "CompoundType", "tags": [], - "label": "data", + "label": "enforce", "description": [], "signature": [ - { - "pluginId": "visualizations", - "scope": "common", - "docId": "kibVisualizationsPluginApi", - "section": "def-common.SerializedVisData", - "text": "SerializedVisData" - } + "boolean | undefined" ], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, "trackAdoption": false } @@ -7927,347 +7619,4168 @@ }, { "parentPluginId": "visualizations", - "id": "def-common.SerializedVisData", + "id": "def-common.BaseColumn", "type": "Interface", "tags": [], - "label": "SerializedVisData", + "label": "BaseColumn", "description": [], - "path": "src/plugins/visualizations/common/types.ts", + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.BaseColumn", + "text": "BaseColumn" + }, + "" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "visualizations", - "id": "def-common.SerializedVisData.expression", + "id": "def-common.BaseColumn.columnId", "type": "string", "tags": [], - "label": "expression", + "label": "columnId", "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-common.SerializedVisData.aggs", - "type": "Array", + "id": "def-common.BaseColumn.operationType", + "type": "Uncategorized", "tags": [], - "label": "aggs", + "label": "operationType", "description": [], "signature": [ - "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; }[]" + "OperationType" ], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-common.SerializedVisData.searchSource", - "type": "Object", + "id": "def-common.BaseColumn.label", + "type": "string", "tags": [], - "label": "searchSource", + "label": "label", "description": [], "signature": [ - "{ type?: string | undefined; query?: ", - "Query", - " | ", - "AggregateQuery", - " | undefined; filter?: ", - "Filter", - "[] | undefined; sort?: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.EsQuerySortValue", - "text": "EsQuerySortValue" - }, - "[] | undefined; highlight?: ", - "SerializableRecord", - " | undefined; highlightAll?: boolean | undefined; trackTotalHits?: number | boolean | undefined; aggs?: { type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; }[] | undefined; from?: number | undefined; size?: number | undefined; source?: boolean | ", - "Fields", - " | undefined; version?: boolean | undefined; fields?: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SearchFieldValue", - "text": "SearchFieldValue" - }, - "[] | undefined; fieldsFromSource?: ", - "Fields", - " | undefined; index?: string | ", - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewSpec", - "text": "DataViewSpec" - }, - " | undefined; searchAfter?: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.EsQuerySearchAfter", - "text": "EsQuerySearchAfter" - }, - " | undefined; timeout?: string | undefined; terminate_after?: number | undefined; parent?: ", - { - "pluginId": "data", - "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.SerializedSearchSourceFields", - "text": "SerializedSearchSourceFields" - }, - " | undefined; }" + "string | undefined" ], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-common.SerializedVisData.savedSearchId", - "type": "string", + "id": "def-common.BaseColumn.isBucketed", + "type": "boolean", "tags": [], - "label": "savedSearchId", + "label": "isBucketed", "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "visualizations", - "id": "def-common.VisParams", - "type": "Interface", - "tags": [], - "label": "VisParams", - "description": [], - "path": "src/plugins/visualizations/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + }, { "parentPluginId": "visualizations", - "id": "def-common.VisParams.Unnamed", - "type": "IndexSignature", + "id": "def-common.BaseColumn.isSplit", + "type": "boolean", "tags": [], - "label": "[key: string]: any", + "label": "isSplit", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.BaseColumn.dataType", + "type": "CompoundType", + "tags": [], + "label": "dataType", "description": [], "signature": [ - "[key: string]: any" + "\"string\" | \"number\" | \"boolean\" | \"date\" | \"document\" | \"ip\" | \"geo_point\" | \"geo_shape\" | \"murmur3\" | \"histogram\"" ], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "visualizations", - "id": "def-common.VisualizationSavedObjectAttributes", - "type": "Interface", - "tags": [], - "label": "VisualizationSavedObjectAttributes", - "description": [], - "signature": [ - { - "pluginId": "visualizations", - "scope": "common", - "docId": "kibVisualizationsPluginApi", - "section": "def-common.VisualizationSavedObjectAttributes", - "text": "VisualizationSavedObjectAttributes" }, - " extends ", - "SavedObjectAttributes" - ], - "path": "src/plugins/visualizations/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "visualizations", - "id": "def-common.VisualizationSavedObjectAttributes.description", - "type": "string", + "id": "def-common.BaseColumn.timeScale", + "type": "CompoundType", "tags": [], - "label": "description", + "label": "timeScale", "description": [], - "path": "src/plugins/visualizations/common/types.ts", + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.TimeScaleUnit", + "text": "TimeScaleUnit" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-common.VisualizationSavedObjectAttributes.kibanaSavedObjectMeta", - "type": "Object", + "id": "def-common.BaseColumn.timeShift", + "type": "string", "tags": [], - "label": "kibanaSavedObjectMeta", + "label": "timeShift", "description": [], "signature": [ - "{ searchSourceJSON: string; }" + "string | undefined" ], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-common.VisualizationSavedObjectAttributes.title", + "id": "def-common.BaseColumn.reducedTimeRange", "type": "string", "tags": [], - "label": "title", + "label": "reducedTimeRange", "description": [], - "path": "src/plugins/visualizations/common/types.ts", + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-common.VisualizationSavedObjectAttributes.version", - "type": "number", + "id": "def-common.BaseColumn.isStaticValue", + "type": "CompoundType", "tags": [], - "label": "version", + "label": "isStaticValue", "description": [], - "path": "src/plugins/visualizations/common/types.ts", + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-common.VisualizationSavedObjectAttributes.visState", - "type": "string", + "id": "def-common.BaseColumn.filter", + "type": "Object", "tags": [], - "label": "visState", + "label": "filter", "description": [], - "path": "src/plugins/visualizations/common/types.ts", + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FilterQuery", + "text": "FilterQuery" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "visualizations", - "id": "def-common.VisualizationSavedObjectAttributes.uiStateJSON", - "type": "string", + "id": "def-common.BaseColumn.params", + "type": "Uncategorized", "tags": [], - "label": "uiStateJSON", + "label": "params", "description": [], - "path": "src/plugins/visualizations/common/types.ts", + "signature": [ + "Params" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, "trackAdoption": false } ], "initialIsOpen": false - } - ], - "enums": [ + }, { "parentPluginId": "visualizations", - "id": "def-common.LegendSize", + "id": "def-common.ColumnWithReferences", + "type": "Interface", + "tags": [], + "label": "ColumnWithReferences", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithReferences", + "text": "ColumnWithReferences" + }, + " extends ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.BaseColumn", + "text": "BaseColumn" + }, + "" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.ColumnWithReferences.references", + "type": "Array", + "tags": [], + "label": "references", + "description": [], + "signature": [ + "string[]" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.ColumnWithSourceField", + "type": "Interface", + "tags": [], + "label": "ColumnWithSourceField", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" + }, + " extends ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.BaseColumn", + "text": "BaseColumn" + }, + "" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.ColumnWithSourceField.sourceField", + "type": "string", + "tags": [], + "label": "sourceField", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.DateHistogramParams", + "type": "Interface", + "tags": [], + "label": "DateHistogramParams", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.DateHistogramParams.interval", + "type": "string", + "tags": [], + "label": "interval", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.DateHistogramParams.ignoreTimeRange", + "type": "CompoundType", + "tags": [], + "label": "ignoreTimeRange", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.DateHistogramParams.includeEmptyRows", + "type": "CompoundType", + "tags": [], + "label": "includeEmptyRows", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.DateHistogramParams.dropPartials", + "type": "CompoundType", + "tags": [], + "label": "dropPartials", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Filter", + "type": "Interface", + "tags": [], + "label": "Filter", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.Filter.input", + "type": "Object", + "tags": [], + "label": "input", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FilterQuery", + "text": "FilterQuery" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Filter.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.FilterQuery", + "type": "Interface", + "tags": [], + "label": "FilterQuery", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.FilterQuery.query", + "type": "CompoundType", + "tags": [], + "label": "query", + "description": [], + "signature": [ + "string | { [key: string]: any; }" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.FilterQuery.language", + "type": "string", + "tags": [], + "label": "language", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.FiltersParams", + "type": "Interface", + "tags": [], + "label": "FiltersParams", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.FiltersParams.filters", + "type": "Array", + "tags": [], + "label": "filters", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.FormatParams", + "type": "Interface", + "tags": [], + "label": "FormatParams", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.FormatParams.format", + "type": "Object", + "tags": [], + "label": "format", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.NumberValueFormat", + "text": "NumberValueFormat" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.FormulaParams", + "type": "Interface", + "tags": [], + "label": "FormulaParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormulaParams", + "text": "FormulaParams" + }, + " extends ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.FormulaParams.formula", + "type": "string", + "tags": [], + "label": "formula", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.FormulaParams.isFormulaBroken", + "type": "CompoundType", + "tags": [], + "label": "isFormulaBroken", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LabelsOrientationConfig", + "type": "Interface", + "tags": [], + "label": "LabelsOrientationConfig", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.LabelsOrientationConfig.x", + "type": "number", + "tags": [], + "label": "x", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LabelsOrientationConfig.yLeft", + "type": "number", + "tags": [], + "label": "yLeft", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LabelsOrientationConfig.yRight", + "type": "number", + "tags": [], + "label": "yRight", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LastValueParams", + "type": "Interface", + "tags": [], + "label": "LastValueParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.LastValueParams", + "text": "LastValueParams" + }, + " extends ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.LastValueParams.sortField", + "type": "string", + "tags": [], + "label": "sortField", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LastValueParams.showArrayValues", + "type": "boolean", + "tags": [], + "label": "showArrayValues", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Layer", + "type": "Interface", + "tags": [], + "label": "Layer", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/context.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.Layer.indexPatternId", + "type": "string", + "tags": [], + "label": "indexPatternId", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/context.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Layer.layerId", + "type": "string", + "tags": [], + "label": "layerId", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/context.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Layer.columns", + "type": "Array", + "tags": [], + "label": "columns", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.Column", + "text": "Column" + }, + "[]" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/context.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Layer.columnOrder", + "type": "Array", + "tags": [], + "label": "columnOrder", + "description": [], + "signature": [ + "string[]" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/context.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LegendConfig", + "type": "Interface", + "tags": [], + "label": "LegendConfig", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.LegendConfig.isVisible", + "type": "boolean", + "tags": [], + "label": "isVisible", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LegendConfig.position", + "type": "CompoundType", + "tags": [], + "label": "position", + "description": [], + "signature": [ + "\"top\" | \"bottom\" | \"left\" | \"right\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LegendConfig.showSingleSeries", + "type": "CompoundType", + "tags": [], + "label": "showSingleSeries", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LegendConfig.isInside", + "type": "CompoundType", + "tags": [], + "label": "isInside", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LegendConfig.horizontalAlignment", + "type": "CompoundType", + "tags": [], + "label": "horizontalAlignment", + "description": [], + "signature": [ + "\"left\" | \"right\" | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LegendConfig.verticalAlignment", + "type": "CompoundType", + "tags": [], + "label": "verticalAlignment", + "description": [], + "signature": [ + "\"top\" | \"bottom\" | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LegendConfig.floatingColumns", + "type": "number", + "tags": [], + "label": "floatingColumns", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LegendConfig.maxLines", + "type": "number", + "tags": [], + "label": "maxLines", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LegendConfig.shouldTruncate", + "type": "CompoundType", + "tags": [], + "label": "shouldTruncate", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LegendConfig.legendSize", + "type": "CompoundType", + "tags": [], + "label": "legendSize", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.LegendSize", + "text": "LegendSize" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MovingAverageParams", + "type": "Interface", + "tags": [], + "label": "MovingAverageParams", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.MovingAverageParams.window", + "type": "number", + "tags": [], + "label": "window", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.NavigateToLensContext", + "type": "Interface", + "tags": [], + "label": "NavigateToLensContext", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.NavigateToLensContext", + "text": "NavigateToLensContext" + }, + "" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/context.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.NavigateToLensContext.layers", + "type": "Array", + "tags": [], + "label": "layers", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.Layer", + "text": "Layer" + }, + "[]" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/context.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.NavigateToLensContext.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/context.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.NavigateToLensContext.configuration", + "type": "Uncategorized", + "tags": [], + "label": "configuration", + "description": [], + "signature": [ + "T" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/context.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.NumberValueFormat", + "type": "Interface", + "tags": [], + "label": "NumberValueFormat", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.NumberValueFormat.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.NumberValueFormat.params", + "type": "Object", + "tags": [], + "label": "params", + "description": [], + "signature": [ + "{ decimals: number; suffix?: string | undefined; } | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.PercentileParams", + "type": "Interface", + "tags": [], + "label": "PercentileParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.PercentileParams", + "text": "PercentileParams" + }, + " extends ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.PercentileParams.percentile", + "type": "number", + "tags": [], + "label": "percentile", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.PercentileRanksParams", + "type": "Interface", + "tags": [], + "label": "PercentileRanksParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.PercentileRanksParams", + "text": "PercentileRanksParams" + }, + " extends ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.PercentileRanksParams.value", + "type": "number", + "tags": [], + "label": "value", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Range", + "type": "Interface", + "tags": [], + "label": "Range", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.Range.from", + "type": "CompoundType", + "tags": [], + "label": "from", + "description": [], + "signature": [ + "number | null" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Range.to", + "type": "CompoundType", + "tags": [], + "label": "to", + "description": [], + "signature": [ + "number | null" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Range.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.RangeParams", + "type": "Interface", + "tags": [], + "label": "RangeParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.RangeParams", + "text": "RangeParams" + }, + " extends ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.RangeParams.type", + "type": "CompoundType", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "\"range\" | \"histogram\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.RangeParams.maxBars", + "type": "CompoundType", + "tags": [], + "label": "maxBars", + "description": [], + "signature": [ + "number | \"auto\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.RangeParams.ranges", + "type": "Array", + "tags": [], + "label": "ranges", + "description": [], + "signature": [ + "Range[]" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.RangeParams.includeEmptyRows", + "type": "CompoundType", + "tags": [], + "label": "includeEmptyRows", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.RangeParams.parentFormat", + "type": "Object", + "tags": [], + "label": "parentFormat", + "description": [], + "signature": [ + "{ id: string; params?: { id?: string | undefined; template?: string | undefined; replaceInfinity?: boolean | undefined; } | undefined; } | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SerializedVis", + "type": "Interface", + "tags": [], + "label": "SerializedVis", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.SerializedVis", + "text": "SerializedVis" + }, + "" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.SerializedVis.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SerializedVis.title", + "type": "string", + "tags": [], + "label": "title", + "description": [], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SerializedVis.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SerializedVis.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SerializedVis.params", + "type": "Uncategorized", + "tags": [], + "label": "params", + "description": [], + "signature": [ + "T" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SerializedVis.uiState", + "type": "Any", + "tags": [], + "label": "uiState", + "description": [], + "signature": [ + "any" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SerializedVis.data", + "type": "Object", + "tags": [], + "label": "data", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.SerializedVisData", + "text": "SerializedVisData" + } + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SerializedVisData", + "type": "Interface", + "tags": [], + "label": "SerializedVisData", + "description": [], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.SerializedVisData.expression", + "type": "string", + "tags": [], + "label": "expression", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SerializedVisData.aggs", + "type": "Array", + "tags": [], + "label": "aggs", + "description": [], + "signature": [ + "{ type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; }[]" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SerializedVisData.searchSource", + "type": "Object", + "tags": [], + "label": "searchSource", + "description": [], + "signature": [ + "{ type?: string | undefined; query?: ", + "Query", + " | ", + "AggregateQuery", + " | undefined; filter?: ", + "Filter", + "[] | undefined; sort?: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.EsQuerySortValue", + "text": "EsQuerySortValue" + }, + "[] | undefined; highlight?: ", + "SerializableRecord", + " | undefined; highlightAll?: boolean | undefined; trackTotalHits?: number | boolean | undefined; aggs?: { type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; }[] | undefined; from?: number | undefined; size?: number | undefined; source?: boolean | ", + "Fields", + " | undefined; version?: boolean | undefined; fields?: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SearchFieldValue", + "text": "SearchFieldValue" + }, + "[] | undefined; fieldsFromSource?: ", + "Fields", + " | undefined; index?: string | ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewSpec", + "text": "DataViewSpec" + }, + " | undefined; searchAfter?: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.EsQuerySearchAfter", + "text": "EsQuerySearchAfter" + }, + " | undefined; timeout?: string | undefined; terminate_after?: number | undefined; parent?: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataSearchPluginApi", + "section": "def-common.SerializedSearchSourceFields", + "text": "SerializedSearchSourceFields" + }, + " | undefined; }" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SerializedVisData.savedSearchId", + "type": "string", + "tags": [], + "label": "savedSearchId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.StaticValueParams", + "type": "Interface", + "tags": [], + "label": "StaticValueParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.StaticValueParams", + "text": "StaticValueParams" + }, + " extends ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.StaticValueParams.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TermsParams", + "type": "Interface", + "tags": [], + "label": "TermsParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.TermsParams", + "text": "TermsParams" + }, + " extends ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.TermsParams.size", + "type": "number", + "tags": [], + "label": "size", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TermsParams.include", + "type": "CompoundType", + "tags": [], + "label": "include", + "description": [], + "signature": [ + "string[] | number[] | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TermsParams.exclude", + "type": "CompoundType", + "tags": [], + "label": "exclude", + "description": [], + "signature": [ + "string[] | number[] | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TermsParams.includeIsRegex", + "type": "CompoundType", + "tags": [], + "label": "includeIsRegex", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TermsParams.excludeIsRegex", + "type": "CompoundType", + "tags": [], + "label": "excludeIsRegex", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TermsParams.orderBy", + "type": "CompoundType", + "tags": [], + "label": "orderBy", + "description": [], + "signature": [ + "{ type: \"alphabetical\"; fallback?: boolean | undefined; } | { type: \"rare\"; maxDocCount: number; } | { type: \"column\"; columnId: string; } | { type: \"custom\"; }" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TermsParams.orderAgg", + "type": "CompoundType", + "tags": [], + "label": "orderAgg", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.Column", + "text": "Column" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TermsParams.orderDirection", + "type": "CompoundType", + "tags": [], + "label": "orderDirection", + "description": [], + "signature": [ + "\"asc\" | \"desc\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TermsParams.otherBucket", + "type": "CompoundType", + "tags": [], + "label": "otherBucket", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TermsParams.missingBucket", + "type": "CompoundType", + "tags": [], + "label": "missingBucket", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TermsParams.secondaryFields", + "type": "Array", + "tags": [], + "label": "secondaryFields", + "description": [], + "signature": [ + "string[] | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TermsParams.parentFormat", + "type": "Object", + "tags": [], + "label": "parentFormat", + "description": [], + "signature": [ + "{ id: string; } | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TimeScaleParams", + "type": "Interface", + "tags": [], + "label": "TimeScaleParams", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.TimeScaleParams.unit", + "type": "string", + "tags": [], + "label": "unit", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.VisParams", + "type": "Interface", + "tags": [], + "label": "VisParams", + "description": [], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.VisParams.Unnamed", + "type": "IndexSignature", + "tags": [], + "label": "[key: string]: any", + "description": [], + "signature": [ + "[key: string]: any" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.VisualizationSavedObjectAttributes", + "type": "Interface", + "tags": [], + "label": "VisualizationSavedObjectAttributes", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.VisualizationSavedObjectAttributes", + "text": "VisualizationSavedObjectAttributes" + }, + " extends ", + "SavedObjectAttributes" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.VisualizationSavedObjectAttributes.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.VisualizationSavedObjectAttributes.kibanaSavedObjectMeta", + "type": "Object", + "tags": [], + "label": "kibanaSavedObjectMeta", + "description": [], + "signature": [ + "{ searchSourceJSON: string; }" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.VisualizationSavedObjectAttributes.title", + "type": "string", + "tags": [], + "label": "title", + "description": [], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.VisualizationSavedObjectAttributes.version", + "type": "number", + "tags": [], + "label": "version", + "description": [], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.VisualizationSavedObjectAttributes.visState", + "type": "string", + "tags": [], + "label": "visState", + "description": [], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.VisualizationSavedObjectAttributes.uiStateJSON", + "type": "string", + "tags": [], + "label": "uiStateJSON", + "description": [], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration", + "type": "Interface", + "tags": [], + "label": "XYConfiguration", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.preferredSeriesType", + "type": "CompoundType", + "tags": [], + "label": "preferredSeriesType", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.SeriesType", + "text": "SeriesType" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.legend", + "type": "Object", + "tags": [], + "label": "legend", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.LegendConfig", + "text": "LegendConfig" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.valueLabels", + "type": "CompoundType", + "tags": [], + "label": "valueLabels", + "description": [], + "signature": [ + "\"hide\" | \"show\" | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.fittingFunction", + "type": "CompoundType", + "tags": [], + "label": "fittingFunction", + "description": [], + "signature": [ + "\"None\" | \"Zero\" | \"Nearest\" | \"Linear\" | \"Carry\" | \"Lookahead\" | \"Average\" | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.emphasizeFitting", + "type": "CompoundType", + "tags": [], + "label": "emphasizeFitting", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.endValue", + "type": "CompoundType", + "tags": [], + "label": "endValue", + "description": [], + "signature": [ + "\"None\" | \"Zero\" | \"Nearest\" | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.xExtent", + "type": "Object", + "tags": [], + "label": "xExtent", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.AxisExtentConfig", + "text": "AxisExtentConfig" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.yLeftExtent", + "type": "Object", + "tags": [], + "label": "yLeftExtent", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.AxisExtentConfig", + "text": "AxisExtentConfig" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.yRightExtent", + "type": "Object", + "tags": [], + "label": "yRightExtent", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.AxisExtentConfig", + "text": "AxisExtentConfig" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.layers", + "type": "Array", + "tags": [], + "label": "layers", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.XYLayerConfig", + "text": "XYLayerConfig" + }, + "[]" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.xTitle", + "type": "string", + "tags": [], + "label": "xTitle", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.yTitle", + "type": "string", + "tags": [], + "label": "yTitle", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.yRightTitle", + "type": "string", + "tags": [], + "label": "yRightTitle", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.yLeftScale", + "type": "CompoundType", + "tags": [], + "label": "yLeftScale", + "description": [], + "signature": [ + "\"linear\" | \"log\" | \"time\" | \"sqrt\" | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.yRightScale", + "type": "CompoundType", + "tags": [], + "label": "yRightScale", + "description": [], + "signature": [ + "\"linear\" | \"log\" | \"time\" | \"sqrt\" | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.axisTitlesVisibilitySettings", + "type": "Object", + "tags": [], + "label": "axisTitlesVisibilitySettings", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.AxesSettingsConfig", + "text": "AxesSettingsConfig" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.tickLabelsVisibilitySettings", + "type": "Object", + "tags": [], + "label": "tickLabelsVisibilitySettings", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.AxesSettingsConfig", + "text": "AxesSettingsConfig" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.gridlinesVisibilitySettings", + "type": "Object", + "tags": [], + "label": "gridlinesVisibilitySettings", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.AxesSettingsConfig", + "text": "AxesSettingsConfig" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.labelsOrientation", + "type": "Object", + "tags": [], + "label": "labelsOrientation", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.LabelsOrientationConfig", + "text": "LabelsOrientationConfig" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.curveType", + "type": "CompoundType", + "tags": [], + "label": "curveType", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.XYCurveType", + "text": "XYCurveType" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.fillOpacity", + "type": "number", + "tags": [], + "label": "fillOpacity", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.hideEndzones", + "type": "CompoundType", + "tags": [], + "label": "hideEndzones", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYConfiguration.valuesInLegend", + "type": "CompoundType", + "tags": [], + "label": "valuesInLegend", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYDataLayerConfig", + "type": "Interface", + "tags": [], + "label": "XYDataLayerConfig", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.XYDataLayerConfig.layerId", + "type": "string", + "tags": [], + "label": "layerId", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYDataLayerConfig.accessors", + "type": "Array", + "tags": [], + "label": "accessors", + "description": [], + "signature": [ + "string[]" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYDataLayerConfig.layerType", + "type": "string", + "tags": [], + "label": "layerType", + "description": [], + "signature": [ + "\"data\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYDataLayerConfig.seriesType", + "type": "CompoundType", + "tags": [], + "label": "seriesType", + "description": [], + "signature": [ + "\"bar\" | \"line\" | \"area\" | \"bar_stacked\" | \"area_stacked\" | \"bar_horizontal\" | \"bar_percentage_stacked\" | \"bar_horizontal_stacked\" | \"area_percentage_stacked\" | \"bar_horizontal_percentage_stacked\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYDataLayerConfig.xAccessor", + "type": "string", + "tags": [], + "label": "xAccessor", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYDataLayerConfig.simpleView", + "type": "CompoundType", + "tags": [], + "label": "simpleView", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYDataLayerConfig.yConfig", + "type": "Array", + "tags": [], + "label": "yConfig", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.YConfig", + "text": "YConfig" + }, + "[] | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYDataLayerConfig.splitAccessor", + "type": "string", + "tags": [], + "label": "splitAccessor", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYDataLayerConfig.palette", + "type": "Object", + "tags": [], + "label": "palette", + "description": [], + "signature": [ + "PaletteOutput", + "<{ [key: string]: unknown; }> | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYDataLayerConfig.collapseFn", + "type": "string", + "tags": [], + "label": "collapseFn", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYDataLayerConfig.xScaleType", + "type": "CompoundType", + "tags": [], + "label": "xScaleType", + "description": [], + "signature": [ + "\"linear\" | \"time\" | \"ordinal\" | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYDataLayerConfig.isHistogram", + "type": "CompoundType", + "tags": [], + "label": "isHistogram", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYDataLayerConfig.columnToLabel", + "type": "string", + "tags": [], + "label": "columnToLabel", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYReferenceLineLayerConfig", + "type": "Interface", + "tags": [], + "label": "XYReferenceLineLayerConfig", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.XYReferenceLineLayerConfig.layerId", + "type": "string", + "tags": [], + "label": "layerId", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYReferenceLineLayerConfig.accessors", + "type": "Array", + "tags": [], + "label": "accessors", + "description": [], + "signature": [ + "string[]" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYReferenceLineLayerConfig.yConfig", + "type": "Array", + "tags": [], + "label": "yConfig", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.YConfig", + "text": "YConfig" + }, + "[] | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYReferenceLineLayerConfig.layerType", + "type": "string", + "tags": [], + "label": "layerType", + "description": [], + "signature": [ + "\"referenceLine\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.YConfig", + "type": "Interface", + "tags": [], + "label": "YConfig", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "visualizations", + "id": "def-common.YConfig.forAccessor", + "type": "string", + "tags": [], + "label": "forAccessor", + "description": [], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.YConfig.color", + "type": "string", + "tags": [], + "label": "color", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.YConfig.icon", + "type": "string", + "tags": [], + "label": "icon", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.YConfig.lineWidth", + "type": "number", + "tags": [], + "label": "lineWidth", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.YConfig.lineStyle", + "type": "CompoundType", + "tags": [], + "label": "lineStyle", + "description": [], + "signature": [ + "\"solid\" | \"dashed\" | \"dotted\" | \"dot-dashed\" | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.YConfig.fill", + "type": "CompoundType", + "tags": [], + "label": "fill", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FillType", + "text": "FillType" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.YConfig.iconPosition", + "type": "CompoundType", + "tags": [], + "label": "iconPosition", + "description": [], + "signature": [ + "\"above\" | \"below\" | \"auto\" | \"left\" | \"right\" | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.YConfig.textVisibility", + "type": "CompoundType", + "tags": [], + "label": "textVisibility", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.YConfig.axisMode", + "type": "CompoundType", + "tags": [], + "label": "axisMode", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.YAxisMode", + "text": "YAxisMode" + }, + " | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [ + { + "parentPluginId": "visualizations", + "id": "def-common.LegendSize", "type": "Enum", "tags": [], - "label": "LegendSize", + "label": "LegendSize", + "description": [], + "path": "src/plugins/visualizations/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "misc": [ + { + "parentPluginId": "visualizations", + "id": "def-common.AnyColumnWithReferences", + "type": "Type", + "tags": [], + "label": "AnyColumnWithReferences", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.CumulativeSumColumn", + "text": "CumulativeSumColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.CounterRateColumn", + "text": "CounterRateColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.DerivativeColumn", + "text": "DerivativeColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.MovingAverageColumn", + "text": "MovingAverageColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormulaColumn", + "text": "FormulaColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.StaticValueColumn", + "text": "StaticValueColumn" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.AnyColumnWithSourceField", + "type": "Type", + "tags": [], + "label": "AnyColumnWithSourceField", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.RangeColumn", + "text": "RangeColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.TermsColumn", + "text": "TermsColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.DateHistogramColumn", + "text": "DateHistogramColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.MinColumn", + "text": "MinColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.MaxColumn", + "text": "MaxColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.AvgColumn", + "text": "AvgColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.SumColumn", + "text": "SumColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.MedianColumn", + "text": "MedianColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.StandardDeviationColumn", + "text": "StandardDeviationColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.CardinalityColumn", + "text": "CardinalityColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.PercentileColumn", + "text": "PercentileColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.PercentileRanksColumn", + "text": "PercentileRanksColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.CountColumn", + "text": "CountColumn" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.LastValueColumn", + "text": "LastValueColumn" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.AvgColumn", + "type": "Type", + "tags": [], + "label": "AvgColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" + }, + "<\"average\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.AvgParams", + "type": "Type", + "tags": [], + "label": "AvgParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.CardinalityColumn", + "type": "Type", + "tags": [], + "label": "CardinalityColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" + }, + "<\"unique_count\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.CardinalityParams", + "type": "Type", + "tags": [], + "label": "CardinalityParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Column", + "type": "Type", + "tags": [], + "label": "Column", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.AnyColumnWithReferences", + "text": "AnyColumnWithReferences" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.AnyColumnWithSourceField", + "text": "AnyColumnWithSourceField" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FiltersColumn", + "text": "FiltersColumn" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.ColumnWithMeta", + "type": "Type", + "tags": [], + "label": "ColumnWithMeta", + "description": [], + "signature": [ + "Col & (Meta extends undefined ? undefined : { meta: Meta; })" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Configuration", + "type": "Type", + "tags": [], + "label": "Configuration", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.XYConfiguration", + "text": "XYConfiguration" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.CountColumn", + "type": "Type", + "tags": [], + "label": "CountColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" + }, + "<\"count\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.CounterRateColumn", + "type": "Type", + "tags": [], + "label": "CounterRateColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithReferences", + "text": "ColumnWithReferences" + }, + "<\"counter_rate\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.CounterRateParams", + "type": "Type", + "tags": [], + "label": "CounterRateParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.CountParams", + "type": "Type", + "tags": [], + "label": "CountParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.CumulativeSumColumn", + "type": "Type", + "tags": [], + "label": "CumulativeSumColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithReferences", + "text": "ColumnWithReferences" + }, + "<\"cumulative_sum\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.CumulativeSumParams", + "type": "Type", + "tags": [], + "label": "CumulativeSumParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.DataType", + "type": "Type", + "tags": [], + "label": "DataType", + "description": [], + "signature": [ + "\"string\" | \"number\" | \"boolean\" | \"date\" | \"document\" | \"ip\" | \"geo_point\" | \"geo_shape\" | \"murmur3\" | \"histogram\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.DateHistogramColumn", + "type": "Type", + "tags": [], + "label": "DateHistogramColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" + }, + "<\"date_histogram\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.DateHistogramParams", + "text": "DateHistogramParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.DEFAULT_LEGEND_SIZE", + "type": "string", + "tags": [], + "label": "DEFAULT_LEGEND_SIZE", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.LegendSize", + "text": "LegendSize" + }, + ".MEDIUM" + ], + "path": "src/plugins/visualizations/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.DerivativeColumn", + "type": "Type", + "tags": [], + "label": "DerivativeColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithReferences", + "text": "ColumnWithReferences" + }, + "<\"differences\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.DerivativeParams", + "type": "Type", + "tags": [], + "label": "DerivativeParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Dimension", + "type": "Type", + "tags": [], + "label": "Dimension", + "description": [], + "signature": [ + "[DimensionColumn[] | undefined, string]" + ], + "path": "src/plugins/visualizations/common/utils/prepare_log_table.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.ExpressionValueVisDimension", + "type": "Type", + "tags": [], + "label": "ExpressionValueVisDimension", + "description": [], + "signature": [ + "{ type: \"vis_dimension\"; } & { accessor: number | ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.DatatableColumn", + "text": "DatatableColumn" + }, + "; format: { id?: string | undefined; params?: Record | undefined; }; }" + ], + "path": "src/plugins/visualizations/common/expression_functions/vis_dimension.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.FillType", + "type": "Type", + "tags": [], + "label": "FillType", + "description": [], + "signature": [ + "\"above\" | \"below\" | \"none\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.FiltersColumn", + "type": "Type", + "tags": [], + "label": "FiltersColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.BaseColumn", + "text": "BaseColumn" + }, + "<\"filters\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FiltersParams", + "text": "FiltersParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.FormulaColumn", + "type": "Type", + "tags": [], + "label": "FormulaColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithReferences", + "text": "ColumnWithReferences" + }, + "<\"formula\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormulaParams", + "text": "FormulaParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.LastValueColumn", + "type": "Type", + "tags": [], + "label": "LastValueColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" + }, + "<\"last_value\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.LastValueParams", + "text": "LastValueParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MaxColumn", + "type": "Type", + "tags": [], + "label": "MaxColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" + }, + "<\"max\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MaxParams", + "type": "Type", + "tags": [], + "label": "MaxParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MedianColumn", + "type": "Type", + "tags": [], + "label": "MedianColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" + }, + "<\"median\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MedianParams", + "type": "Type", + "tags": [], + "label": "MedianParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MinColumn", + "type": "Type", + "tags": [], + "label": "MinColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" + }, + "<\"min\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MinParams", + "type": "Type", + "tags": [], + "label": "MinParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.MovingAverageColumn", + "type": "Type", + "tags": [], + "label": "MovingAverageColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithReferences", + "text": "ColumnWithReferences" + }, + "<\"moving_average\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.MovingAverageParams", + "text": "MovingAverageParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Operation", + "type": "Type", + "tags": [], + "label": "Operation", + "description": [], + "signature": [ + "\"range\" | \"min\" | \"max\" | \"filters\" | \"count\" | \"date_histogram\" | \"percentile\" | \"sum\" | \"average\" | \"terms\" | \"median\" | \"cumulative_sum\" | \"moving_average\" | \"counter_rate\" | \"differences\" | \"formula\" | \"static_value\" | \"standard_deviation\" | \"unique_count\" | \"percentile_rank\" | \"last_value\" | \"normalize_by_unit\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/operations.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.OperationWithReferences", + "type": "Type", + "tags": [], + "label": "OperationWithReferences", + "description": [], + "signature": [ + "\"cumulative_sum\" | \"moving_average\" | \"counter_rate\" | \"differences\" | \"formula\" | \"static_value\" | \"normalize_by_unit\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/operations.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.OperationWithSourceField", + "type": "Type", + "tags": [], + "label": "OperationWithSourceField", + "description": [], + "signature": [ + "\"range\" | \"min\" | \"max\" | \"filters\" | \"count\" | \"date_histogram\" | \"percentile\" | \"sum\" | \"average\" | \"terms\" | \"median\" | \"standard_deviation\" | \"unique_count\" | \"percentile_rank\" | \"last_value\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/operations.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.PercentileColumn", + "type": "Type", + "tags": [], + "label": "PercentileColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" + }, + "<\"percentile\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.PercentileParams", + "text": "PercentileParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.PercentileRanksColumn", + "type": "Type", + "tags": [], + "label": "PercentileRanksColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" + }, + "<\"percentile_rank\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.PercentileRanksParams", + "text": "PercentileRanksParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.RangeColumn", + "type": "Type", + "tags": [], + "label": "RangeColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" + }, + "<\"range\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.RangeParams", + "text": "RangeParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SavedVisState", + "type": "Type", + "tags": [], + "label": "SavedVisState", + "description": [], + "signature": [ + "{ title: string; type: string; params: TVisParams; aggs: { type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", + "SerializableRecord", + " | undefined; schema?: string | undefined; }[]; }" + ], + "path": "src/plugins/visualizations/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SeriesType", + "type": "Type", + "tags": [], + "label": "SeriesType", "description": [], - "path": "src/plugins/visualizations/common/constants.ts", + "signature": [ + "\"bar\" | \"line\" | \"area\" | \"bar_stacked\" | \"area_stacked\" | \"bar_horizontal\" | \"bar_percentage_stacked\" | \"bar_horizontal_stacked\" | \"area_percentage_stacked\" | \"bar_horizontal_percentage_stacked\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false - } - ], - "misc": [ + }, { "parentPluginId": "visualizations", - "id": "def-common.DEFAULT_LEGEND_SIZE", - "type": "string", + "id": "def-common.SortingHint", + "type": "Type", "tags": [], - "label": "DEFAULT_LEGEND_SIZE", + "label": "SortingHint", + "description": [], + "signature": [ + "\"version\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.StandardDeviationColumn", + "type": "Type", + "tags": [], + "label": "StandardDeviationColumn", "description": [], "signature": [ { "pluginId": "visualizations", "scope": "common", "docId": "kibVisualizationsPluginApi", - "section": "def-common.LegendSize", - "text": "LegendSize" + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" }, - ".MEDIUM" + "<\"standard_deviation\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + }, + ">" ], - "path": "src/plugins/visualizations/common/constants.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "visualizations", - "id": "def-common.Dimension", + "id": "def-common.StandardDeviationParams", "type": "Type", "tags": [], - "label": "Dimension", + "label": "StandardDeviationParams", "description": [], "signature": [ - "[DimensionColumn[] | undefined, string]" + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } ], - "path": "src/plugins/visualizations/common/utils/prepare_log_table.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "visualizations", - "id": "def-common.ExpressionValueVisDimension", + "id": "def-common.StaticValueColumn", "type": "Type", "tags": [], - "label": "ExpressionValueVisDimension", + "label": "StaticValueColumn", "description": [], "signature": [ - "{ type: \"vis_dimension\"; } & { accessor: number | ", { - "pluginId": "expressions", + "pluginId": "visualizations", "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.DatatableColumn", - "text": "DatatableColumn" + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithReferences", + "text": "ColumnWithReferences" }, - "; format: { id?: string | undefined; params?: Record | undefined; }; }" + "<\"static_value\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.StaticValueParams", + "text": "StaticValueParams" + }, + ">" ], - "path": "src/plugins/visualizations/common/expression_functions/vis_dimension.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "visualizations", - "id": "def-common.SavedVisState", + "id": "def-common.SumColumn", "type": "Type", "tags": [], - "label": "SavedVisState", + "label": "SumColumn", "description": [], "signature": [ - "{ title: string; type: string; params: TVisParams; aggs: { type: string; enabled?: boolean | undefined; id?: string | undefined; params?: {} | ", - "SerializableRecord", - " | undefined; schema?: string | undefined; }[]; }" + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" + }, + "<\"sum\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + }, + ">" ], - "path": "src/plugins/visualizations/common/types.ts", + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SumParams", + "type": "Type", + "tags": [], + "label": "SumParams", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.FormatParams", + "text": "FormatParams" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/params.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TermsColumn", + "type": "Type", + "tags": [], + "label": "TermsColumn", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.ColumnWithSourceField", + "text": "ColumnWithSourceField" + }, + "<\"terms\", ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.TermsParams", + "text": "TermsParams" + }, + ">" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/columns.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.TimeScaleUnit", + "type": "Type", + "tags": [], + "label": "TimeScaleUnit", + "description": [], + "signature": [ + "\"d\" | \"h\" | \"m\" | \"s\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/common.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYCurveType", + "type": "Type", + "tags": [], + "label": "XYCurveType", + "description": [], + "signature": [ + "\"LINEAR\" | \"CURVE_MONOTONE_X\" | \"CURVE_STEP_AFTER\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYLayerConfig", + "type": "Type", + "tags": [], + "label": "XYLayerConfig", + "description": [], + "signature": [ + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.XYDataLayerConfig", + "text": "XYDataLayerConfig" + }, + " | ", + { + "pluginId": "visualizations", + "scope": "common", + "docId": "kibVisualizationsPluginApi", + "section": "def-common.XYReferenceLineLayerConfig", + "text": "XYReferenceLineLayerConfig" + } + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.YAxisMode", + "type": "Type", + "tags": [], + "label": "YAxisMode", + "description": [], + "signature": [ + "\"auto\" | \"bottom\" | \"left\" | \"right\"" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false } ], "objects": [ + { + "parentPluginId": "visualizations", + "id": "def-common.FillTypes", + "type": "Object", + "tags": [], + "label": "FillTypes", + "description": [], + "signature": [ + "{ readonly NONE: \"none\"; readonly ABOVE: \"above\"; readonly BELOW: \"below\"; }" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "visualizations", "id": "def-common.LegendSizeToPixels", @@ -8282,6 +11795,96 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.Operations", + "type": "Object", + "tags": [], + "label": "Operations", + "description": [], + "signature": [ + "{ readonly CUMULATIVE_SUM: \"cumulative_sum\"; readonly COUNTER_RATE: \"counter_rate\"; readonly DIFFERENCES: \"differences\"; readonly MOVING_AVERAGE: \"moving_average\"; readonly FORMULA: \"formula\"; readonly STATIC_VALUE: \"static_value\"; readonly NORMALIZE_BY_UNIT: \"normalize_by_unit\"; readonly FILTERS: \"filters\"; readonly RANGE: \"range\"; readonly TERMS: \"terms\"; readonly DATE_HISTOGRAM: \"date_histogram\"; readonly MIN: \"min\"; readonly MAX: \"max\"; readonly AVERAGE: \"average\"; readonly SUM: \"sum\"; readonly MEDIAN: \"median\"; readonly STANDARD_DEVIATION: \"standard_deviation\"; readonly UNIQUE_COUNT: \"unique_count\"; readonly PERCENTILE: \"percentile\"; readonly PERCENTILE_RANK: \"percentile_rank\"; readonly COUNT: \"count\"; readonly LAST_VALUE: \"last_value\"; }" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.OperationsWithReferences", + "type": "Object", + "tags": [], + "label": "OperationsWithReferences", + "description": [], + "signature": [ + "{ readonly CUMULATIVE_SUM: \"cumulative_sum\"; readonly COUNTER_RATE: \"counter_rate\"; readonly DIFFERENCES: \"differences\"; readonly MOVING_AVERAGE: \"moving_average\"; readonly FORMULA: \"formula\"; readonly STATIC_VALUE: \"static_value\"; readonly NORMALIZE_BY_UNIT: \"normalize_by_unit\"; }" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.OperationsWithSourceField", + "type": "Object", + "tags": [], + "label": "OperationsWithSourceField", + "description": [], + "signature": [ + "{ readonly FILTERS: \"filters\"; readonly RANGE: \"range\"; readonly TERMS: \"terms\"; readonly DATE_HISTOGRAM: \"date_histogram\"; readonly MIN: \"min\"; readonly MAX: \"max\"; readonly AVERAGE: \"average\"; readonly SUM: \"sum\"; readonly MEDIAN: \"median\"; readonly STANDARD_DEVIATION: \"standard_deviation\"; readonly UNIQUE_COUNT: \"unique_count\"; readonly PERCENTILE: \"percentile\"; readonly PERCENTILE_RANK: \"percentile_rank\"; readonly COUNT: \"count\"; readonly LAST_VALUE: \"last_value\"; }" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.SeriesTypes", + "type": "Object", + "tags": [], + "label": "SeriesTypes", + "description": [], + "signature": [ + "{ readonly BAR: \"bar\"; readonly LINE: \"line\"; readonly AREA: \"area\"; readonly BAR_STACKED: \"bar_stacked\"; readonly AREA_STACKED: \"area_stacked\"; readonly BAR_HORIZONTAL: \"bar_horizontal\"; readonly BAR_PERCENTAGE_STACKED: \"bar_percentage_stacked\"; readonly BAR_HORIZONTAL_STACKED: \"bar_horizontal_stacked\"; readonly AREA_PERCENTAGE_STACKED: \"area_percentage_stacked\"; readonly BAR_HORIZONTAL_PERCENTAGE_STACKED: \"bar_horizontal_percentage_stacked\"; }" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.XYCurveTypes", + "type": "Object", + "tags": [], + "label": "XYCurveTypes", + "description": [], + "signature": [ + "{ readonly LINEAR: \"LINEAR\"; readonly CURVE_MONOTONE_X: \"CURVE_MONOTONE_X\"; readonly CURVE_STEP_AFTER: \"CURVE_STEP_AFTER\"; }" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.YAxisModes", + "type": "Object", + "tags": [], + "label": "YAxisModes", + "description": [], + "signature": [ + "{ readonly AUTO: \"auto\"; readonly LEFT: \"left\"; readonly RIGHT: \"right\"; readonly BOTTOM: \"bottom\"; }" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ] } diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 974e2fda9781f..cffd0e805a392 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: 2022-09-15 +date: 2022-09-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 419 | 12 | 390 | 15 | +| 611 | 12 | 582 | 14 | ## Client diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 08b4096098829..9fd9bf84f09d5 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -577,6 +577,10 @@ Elastic. |This plugin helps users learn how to use the Painless scripting language. +|{kib-repo}blob/{branch}/x-pack/plugins/profiling/README.md[profiling] +|undefined + + |{kib-repo}blob/{branch}/x-pack/plugins/remote_clusters/README.md[remoteClusters] |This plugin helps users manage their remote clusters, which enable cross-cluster search and cross-cluster replication. @@ -641,6 +645,10 @@ the alertTypes by the Stack in the alerting plugin, register associated HTTP routes, etc. +|{kib-repo}blob/{branch}/x-pack/plugins/stack_connectors/README.md[stackConnectors] +|The stack_connectors plugin provides connector types shipped with Kibana, built on top of the framework provided in the actions plugin. + + |{kib-repo}blob/{branch}/x-pack/plugins/synthetics/README.md[synthetics] |The purpose of this plugin is to provide users of Heartbeat more visibility of what's happening in their infrastructure. diff --git a/package.json b/package.json index e7e9092fa0f11..e1d0340c23da5 100644 --- a/package.json +++ b/package.json @@ -146,6 +146,7 @@ "@kbn/config": "link:bazel-bin/packages/kbn-config", "@kbn/config-mocks": "link:bazel-bin/packages/kbn-config-mocks", "@kbn/config-schema": "link:bazel-bin/packages/kbn-config-schema", + "@kbn/content-management-table-list": "link:bazel-bin/packages/content-management/table_list", "@kbn/core-analytics-browser": "link:bazel-bin/packages/core/analytics/core-analytics-browser", "@kbn/core-analytics-browser-internal": "link:bazel-bin/packages/core/analytics/core-analytics-browser-internal", "@kbn/core-analytics-browser-mocks": "link:bazel-bin/packages/core/analytics/core-analytics-browser-mocks", @@ -433,7 +434,7 @@ "constate": "^3.3.2", "content-disposition": "^0.5.4", "copy-to-clipboard": "^3.0.8", - "core-js": "^3.25.0", + "core-js": "^3.25.1", "cronstrue": "^1.51.0", "cuid": "^2.1.8", "cytoscape": "^3.10.0", @@ -461,6 +462,7 @@ "fast-deep-equal": "^3.1.1", "fflate": "^0.6.9", "file-saver": "^1.3.8", + "fnv-plus": "^1.3.1", "font-awesome": "4.7.0", "formik": "^2.2.9", "fp-ts": "^2.3.1", @@ -534,6 +536,7 @@ "pbf": "3.2.1", "pdfjs-dist": "^2.13.216", "pdfmake": "^0.2.5", + "peggy": "^1.2.0", "pluralize": "3.1.0", "polished": "^3.7.2", "pretty-ms": "6.0.0", @@ -554,7 +557,7 @@ "react-fast-compare": "^2.0.4", "react-focus-on": "^3.6.0", "react-grid-layout": "^1.3.4", - "react-hook-form": "^7.34.2", + "react-hook-form": "^7.35.0", "react-intl": "^2.8.0", "react-is": "^17.0.2", "react-markdown": "^6.0.3", @@ -774,6 +777,7 @@ "@types/d3-time": "^1.0.10", "@types/d3-time-format": "^2.1.1", "@types/d3-transition": "^3.0.1", + "@types/dagre": "^0.7.47", "@types/dedent": "^0.7.0", "@types/deep-freeze-strict": "^1.1.0", "@types/delete-empty": "^2.0.0", @@ -786,6 +790,7 @@ "@types/fetch-mock": "^7.3.1", "@types/file-saver": "^2.0.0", "@types/flot": "^0.0.31", + "@types/fnv-plus": "^1.3.0", "@types/geojson": "7946.0.7", "@types/getos": "^3.0.0", "@types/gulp": "^4.0.6", @@ -839,6 +844,7 @@ "@types/kbn__config": "link:bazel-bin/packages/kbn-config/npm_module_types", "@types/kbn__config-mocks": "link:bazel-bin/packages/kbn-config-mocks/npm_module_types", "@types/kbn__config-schema": "link:bazel-bin/packages/kbn-config-schema/npm_module_types", + "@types/kbn__content-management-table-list": "link:bazel-bin/packages/content-management/table_list/npm_module_types", "@types/kbn__core-analytics-browser": "link:bazel-bin/packages/core/analytics/core-analytics-browser/npm_module_types", "@types/kbn__core-analytics-browser-internal": "link:bazel-bin/packages/core/analytics/core-analytics-browser-internal/npm_module_types", "@types/kbn__core-analytics-browser-mocks": "link:bazel-bin/packages/core/analytics/core-analytics-browser-mocks/npm_module_types", @@ -1101,6 +1107,7 @@ "@types/kbn__telemetry-tools": "link:bazel-bin/packages/kbn-telemetry-tools/npm_module_types", "@types/kbn__test": "link:bazel-bin/packages/kbn-test/npm_module_types", "@types/kbn__test-jest-helpers": "link:bazel-bin/packages/kbn-test-jest-helpers/npm_module_types", + "@types/kbn__test-subj-selector": "link:bazel-bin/packages/kbn-test-subj-selector/npm_module_types", "@types/kbn__tooling-log": "link:bazel-bin/packages/kbn-tooling-log/npm_module_types", "@types/kbn__type-summarizer": "link:bazel-bin/packages/kbn-type-summarizer/npm_module_types", "@types/kbn__type-summarizer-cli": "link:bazel-bin/packages/kbn-type-summarizer-cli/npm_module_types", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 6f40fc9ae3864..f55c3c39befb1 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -14,6 +14,7 @@ filegroup( "//packages/analytics/shippers/elastic_v3/common:build", "//packages/analytics/shippers/elastic_v3/server:build", "//packages/analytics/shippers/fullstory:build", + "//packages/content-management/table_list:build", "//packages/core/analytics/core-analytics-browser:build", "//packages/core/analytics/core-analytics-browser-internal:build", "//packages/core/analytics/core-analytics-browser-mocks:build", @@ -327,6 +328,7 @@ filegroup( "//packages/analytics/shippers/elastic_v3/common:build_types", "//packages/analytics/shippers/elastic_v3/server:build_types", "//packages/analytics/shippers/fullstory:build_types", + "//packages/content-management/table_list:build_types", "//packages/core/analytics/core-analytics-browser:build_types", "//packages/core/analytics/core-analytics-browser-internal:build_types", "//packages/core/analytics/core-analytics-browser-mocks:build_types", @@ -562,6 +564,7 @@ filegroup( "//packages/kbn-telemetry-tools:build_types", "//packages/kbn-test:build_types", "//packages/kbn-test-jest-helpers:build_types", + "//packages/kbn-test-subj-selector:build_types", "//packages/kbn-tooling-log:build_types", "//packages/kbn-type-summarizer:build_types", "//packages/kbn-type-summarizer-cli:build_types", diff --git a/packages/content-management/.storybook/main.js b/packages/content-management/.storybook/main.js new file mode 100644 index 0000000000000..0aaf1046299de --- /dev/null +++ b/packages/content-management/.storybook/main.js @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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. + */ + +const defaultConfig = require('@kbn/storybook').defaultConfig; + +module.exports = { + ...defaultConfig, + stories: ['../**/*.stories.tsx'], + reactOptions: { + strictMode: true, + }, +}; diff --git a/packages/content-management/.storybook/manager.js b/packages/content-management/.storybook/manager.js new file mode 100644 index 0000000000000..bc576ed60a8aa --- /dev/null +++ b/packages/content-management/.storybook/manager.js @@ -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 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. + */ +const { addons } = require('@storybook/addons'); +const { create } = require('@storybook/theming'); +const { PANEL_ID } = require('@storybook/addon-actions'); + +addons.setConfig({ + theme: create({ + base: 'light', + brandTitle: 'Content Management Storybook', + }), + showPanel: () => true, + selectedPanel: PANEL_ID, +}); diff --git a/packages/content-management/table_list/BUILD.bazel b/packages/content-management/table_list/BUILD.bazel new file mode 100644 index 0000000000000..36d84a426a017 --- /dev/null +++ b/packages/content-management/table_list/BUILD.bazel @@ -0,0 +1,160 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "table_list" +PKG_REQUIRE_NAME = "@kbn/content-management-table-list" + +SOURCE_FILES = glob( + [ + "**/*.ts", + "**/*.tsx", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__", + "**/integration_tests", + "**/mocks", + "**/scripts", + "**/storybook", + "**/test_fixtures", + "**/test_helpers", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +# In this array place runtime dependencies, including other packages and NPM packages +# which must be available for this code to run. +# +# To reference other packages use: +# "//repo/relative/path/to/package" +# eg. "//packages/kbn-utils" +# +# To reference a NPM package use: +# "@npm//name-of-package" +# eg. "@npm//lodash" +RUNTIME_DEPS = [ + "//packages/kbn-i18n-react", + "//packages/kbn-i18n", + "//packages/core/http/core-http-browser", + "//packages/core/theme/core-theme-browser", + "//packages/kbn-safer-lodash-set", + "//packages/shared-ux/page/kibana_template/impl", + "@npm//@elastic/eui", + "@npm//@emotion/react", + "@npm//@emotion/css", + "@npm//lodash", + "@npm//moment", + "@npm//react-use", + "@npm//react", + "@npm//rxjs", +] + +# In this array place dependencies necessary to build the types, which will include the +# :npm_module_types target of other packages and packages from NPM, including @types/* +# packages. +# +# To reference the types for another package use: +# "//repo/relative/path/to/package:npm_module_types" +# eg. "//packages/kbn-utils:npm_module_types" +# +# References to NPM packages work the same as RUNTIME_DEPS +TYPES_DEPS = [ + "//packages/kbn-i18n:npm_module_types", + "//packages/kbn-i18n-react:npm_module_types", + "//packages/core/http/core-http-browser:npm_module_types", + "//packages/core/theme/core-theme-browser:npm_module_types", + "//packages/kbn-ambient-storybook-types", + "//packages/kbn-ambient-ui-types", + "//packages/kbn-safer-lodash-set:npm_module_types", + "//packages/shared-ux/page/kibana_template/impl:npm_module_types", + "//packages/shared-ux/page/kibana_template/types", + "@npm//@types/node", + "@npm//@types/jest", + "@npm//@types/lodash", + "@npm//@types/react", + "@npm//@elastic/eui", + "@npm//react-use", + "@npm//rxjs", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/content-management/table_list/README.mdx b/packages/content-management/table_list/README.mdx new file mode 100644 index 0000000000000..1847207c3bdcd --- /dev/null +++ b/packages/content-management/table_list/README.mdx @@ -0,0 +1,20 @@ +--- +id: sharedUX/contentManagement/TableList +slug: /shared-ux/content-management/table-list +title: Table list view +summary: A table to render user generated saved objects. +tags: ['shared-ux', 'content-management'] +date: 2022-08-09 +--- + +The `` render a eui page to display a list of user content saved object. + +**Uncomplete documentation**. Will be updated. + +## API + +TODO + +## EUI Promotion Status + +This component is not currently considered for promotion to EUI. diff --git a/packages/content-management/table_list/index.ts b/packages/content-management/table_list/index.ts new file mode 100644 index 0000000000000..c6550a12da30a --- /dev/null +++ b/packages/content-management/table_list/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { TableListView, TableListViewProvider, TableListViewKibanaProvider } from './src'; + +export type { UserContentCommonSchema } from './src'; diff --git a/packages/content-management/table_list/jest.config.js b/packages/content-management/table_list/jest.config.js new file mode 100644 index 0000000000000..546d16dd86cf0 --- /dev/null +++ b/packages/content-management/table_list/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/packages/content-management/table_list'], +}; diff --git a/packages/content-management/table_list/kibana.jsonc b/packages/content-management/table_list/kibana.jsonc new file mode 100644 index 0000000000000..7f22b8c8f56c4 --- /dev/null +++ b/packages/content-management/table_list/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/content-management-table-list", + "owner": "@elastic/shared-ux", + "runtimeDeps": [], + "typeDeps": [] +} diff --git a/packages/content-management/table_list/package.json b/packages/content-management/table_list/package.json new file mode 100644 index 0000000000000..f4cc8ba690d20 --- /dev/null +++ b/packages/content-management/table_list/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/content-management-table-list", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "browser": "./target_web/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/kbn-test-subj-selector/index.d.ts b/packages/content-management/table_list/src/__jest__/index.ts similarity index 74% rename from packages/kbn-test-subj-selector/index.d.ts rename to packages/content-management/table_list/src/__jest__/index.ts index 41765c094dc74..2f3d8863ff1d6 100644 --- a/packages/kbn-test-subj-selector/index.d.ts +++ b/packages/content-management/table_list/src/__jest__/index.ts @@ -6,5 +6,4 @@ * Side Public License, v 1. */ -// eslint-disable-next-line import/no-default-export -export default function kbnTestSubjSelector(selector: string): string; +export { WithServices } from './tests.helpers'; diff --git a/packages/content-management/table_list/src/__jest__/tests.helpers.tsx b/packages/content-management/table_list/src/__jest__/tests.helpers.tsx new file mode 100644 index 0000000000000..381e4974b4e36 --- /dev/null +++ b/packages/content-management/table_list/src/__jest__/tests.helpers.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import type { ComponentType } from 'react'; +import { from } from 'rxjs'; + +import { TableListViewProvider, Services } from '../services'; + +export const getMockServices = (overrides?: Partial) => { + const services: Services = { + canEditAdvancedSettings: true, + getListingLimitSettingsUrl: () => 'http://elastic.co', + notifyError: () => undefined, + currentAppId$: from('mockedApp'), + navigateToUrl: () => undefined, + ...overrides, + }; + + return services; +}; + +export function WithServices

(Comp: ComponentType

, overrides: Partial = {}) { + return (props: P) => { + const services = getMockServices(overrides); + return ( + + + + ); + }; +} diff --git a/packages/content-management/table_list/src/actions.ts b/packages/content-management/table_list/src/actions.ts new file mode 100644 index 0000000000000..ad82aa7379812 --- /dev/null +++ b/packages/content-management/table_list/src/actions.ts @@ -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 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 { IHttpFetchError } from '@kbn/core-http-browser'; +import type { CriteriaWithPagination } from '@elastic/eui'; + +/** Action to trigger a fetch of the table items */ +export interface OnFetchItemsAction { + type: 'onFetchItems'; +} + +/** Action to return the fetched table items */ +export interface OnFetchItemsSuccessAction { + type: 'onFetchItemsSuccess'; + data: { + response: { + total: number; + hits: T[]; + }; + }; +} + +/** Action to return any error while fetching the table items */ +export interface OnFetchItemsErrorAction { + type: 'onFetchItemsError'; + data: IHttpFetchError; +} + +/** + * Actions to update the state of items deletions + * - onDeleteItems: emit before deleting item(s) + * - onItemsDeleted: emit after deleting item(s) + * - onCancelDeleteItems: emit to cancel deleting items (and close the modal) + */ +export interface DeleteItemsActions { + type: 'onCancelDeleteItems' | 'onDeleteItems' | 'onItemsDeleted'; +} + +/** Action to update the selection of items in the table (for batch operations) */ +export interface OnSelectionChangeAction { + type: 'onSelectionChange'; + data: T[]; +} + +/** Action to update the state of the table whenever the sort or page size changes */ +export interface OnTableChangeAction { + type: 'onTableChange'; + data: CriteriaWithPagination; +} + +/** Action to display the delete confirmation modal */ +export interface ShowConfirmDeleteItemsModalAction { + type: 'showConfirmDeleteItemsModal'; +} + +/** Action to update the search bar query text */ +export interface OnSearchQueryChangeAction { + type: 'onSearchQueryChange'; + data: string; +} + +export type Action = + | OnFetchItemsAction + | OnFetchItemsSuccessAction + | OnFetchItemsErrorAction + | DeleteItemsActions + | OnSelectionChangeAction + | OnTableChangeAction + | ShowConfirmDeleteItemsModalAction + | OnSearchQueryChangeAction; diff --git a/packages/content-management/table_list/src/components/confirm_delete_modal.tsx b/packages/content-management/table_list/src/components/confirm_delete_modal.tsx new file mode 100644 index 0000000000000..af2585788701a --- /dev/null +++ b/packages/content-management/table_list/src/components/confirm_delete_modal.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { EuiConfirmModal } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +function getI18nTexts(items: unknown[], entityName: string, entityNamePlural: string) { + return { + deleteBtnLabel: i18n.translate( + 'contentManagement.tableList.listing.deleteSelectedItemsConfirmModal.confirmButtonLabel', + { + defaultMessage: 'Delete', + } + ), + deletingBtnLabel: i18n.translate( + 'contentManagement.tableList.listing.deleteSelectedItemsConfirmModal.confirmButtonLabelDeleting', + { + defaultMessage: 'Deleting', + } + ), + title: i18n.translate('contentManagement.tableList.listing.deleteSelectedConfirmModal.title', { + defaultMessage: 'Delete {itemCount} {entityName}?', + values: { + itemCount: items.length, + entityName: items.length === 1 ? entityName : entityNamePlural, + }, + }), + description: i18n.translate( + 'contentManagement.tableList.listing.deleteConfirmModalDescription', + { + defaultMessage: `You can't recover deleted {entityNamePlural}.`, + values: { + entityNamePlural, + }, + } + ), + cancelBtnLabel: i18n.translate( + 'contentManagement.tableList.listing.deleteSelectedItemsConfirmModal.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + ), + }; +} + +interface Props { + /** Flag to indicate if the items are being deleted */ + isDeletingItems: boolean; + /** Array of items to delete */ + items: T[]; + /** The name of the entity to delete (singular) */ + entityName: string; + /** The name of the entity to delete (plural) */ + entityNamePlural: string; + /** Handler to be called when clicking the "Cancel" button */ + onCancel: () => void; + /** Handler to be called when clicking the "Confirm" button */ + onConfirm: () => void; +} + +export function ConfirmDeleteModal({ + isDeletingItems, + items, + entityName, + entityNamePlural, + onCancel, + onConfirm, +}: Props) { + const { deleteBtnLabel, deletingBtnLabel, title, description, cancelBtnLabel } = getI18nTexts( + items, + entityName, + entityNamePlural + ); + + return ( + +

{description}

+ + ); +} diff --git a/packages/content-management/table_list/src/components/index.ts b/packages/content-management/table_list/src/components/index.ts new file mode 100644 index 0000000000000..b9226155ba44d --- /dev/null +++ b/packages/content-management/table_list/src/components/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { Table } from './table'; +export { UpdatedAtField } from './updated_at_field'; +export { ConfirmDeleteModal } from './confirm_delete_modal'; +export { ListingLimitWarning } from './listing_limit_warning'; diff --git a/packages/content-management/table_list/src/components/listing_limit_warning.tsx b/packages/content-management/table_list/src/components/listing_limit_warning.tsx new file mode 100644 index 0000000000000..f5317a9598baa --- /dev/null +++ b/packages/content-management/table_list/src/components/listing_limit_warning.tsx @@ -0,0 +1,78 @@ +/* + * Copyright 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 { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +interface Props { + entityNamePlural: string; + canEditAdvancedSettings: boolean; + advancedSettingsLink: string; + totalItems: number; + listingLimit: number; +} + +export function ListingLimitWarning({ + entityNamePlural, + totalItems, + listingLimit, + canEditAdvancedSettings, + advancedSettingsLink, +}: Props) { + return ( + <> + + } + color="warning" + iconType="help" + > +

+ listingLimit, + }} + />{' '} + {canEditAdvancedSettings ? ( + + + + ), + }} + /> + ) : ( + + )} +

+
+ + + ); +} diff --git a/packages/content-management/table_list/src/components/table.tsx b/packages/content-management/table_list/src/components/table.tsx new file mode 100644 index 0000000000000..feb83dbc30a40 --- /dev/null +++ b/packages/content-management/table_list/src/components/table.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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, { Dispatch, useCallback } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiBasicTableColumn, + EuiButton, + EuiInMemoryTable, + CriteriaWithPagination, + PropertySort, +} from '@elastic/eui'; + +import { useServices } from '../services'; +import type { Action } from '../actions'; +import type { + State as TableListViewState, + Props as TableListViewProps, + UserContentCommonSchema, +} from '../table_list_view'; + +type State = Pick< + TableListViewState, + 'items' | 'selectedIds' | 'searchQuery' | 'tableSort' | 'pagination' +>; + +interface Props extends State { + dispatch: Dispatch>; + entityName: string; + entityNamePlural: string; + isFetchingItems: boolean; + tableCaption: string; + tableColumns: Array>; + deleteItems: TableListViewProps['deleteItems']; +} + +export function Table({ + dispatch, + items, + isFetchingItems, + searchQuery, + selectedIds, + pagination, + tableColumns, + tableSort, + entityName, + entityNamePlural, + deleteItems, + tableCaption, +}: Props) { + const { getSearchBarFilters } = useServices(); + + const renderToolsLeft = useCallback(() => { + if (!deleteItems || selectedIds.length === 0) { + return; + } + + return ( + dispatch({ type: 'showConfirmDeleteItemsModal' })} + data-test-subj="deleteSelectedItems" + > + + + ); + }, [deleteItems, dispatch, entityName, entityNamePlural, selectedIds.length]); + + const selection = deleteItems + ? { + onSelectionChange: (obj: T[]) => { + dispatch({ type: 'onSelectionChange', data: obj }); + }, + } + : undefined; + + const searchFilters = getSearchBarFilters ? getSearchBarFilters() : []; + + const search = { + onChange: ({ queryText }: { queryText: string }) => + dispatch({ type: 'onSearchQueryChange', data: queryText }), + toolsLeft: renderToolsLeft(), + defaultQuery: searchQuery, + box: { + incremental: true, + 'data-test-subj': 'tableListSearchBox', + }, + filters: searchFilters, + }; + + const noItemsMessage = ( + + ); + + return ( + + itemId="id" + items={items} + columns={tableColumns} + pagination={pagination} + loading={isFetchingItems} + message={noItemsMessage} + selection={selection} + search={search} + sorting={tableSort ? { sort: tableSort as PropertySort } : undefined} + onChange={(criteria: CriteriaWithPagination) => + dispatch({ type: 'onTableChange', data: criteria }) + } + data-test-subj="itemsInMemTable" + rowHeader="attributes.title" + tableCaption={tableCaption} + /> + ); +} diff --git a/packages/content-management/table_list/src/components/updated_at_field.tsx b/packages/content-management/table_list/src/components/updated_at_field.tsx new file mode 100644 index 0000000000000..76055c63f00e4 --- /dev/null +++ b/packages/content-management/table_list/src/components/updated_at_field.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 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, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiToolTip } from '@elastic/eui'; +import moment from 'moment'; + +import { DateFormatter } from '../services'; + +const DefaultDateFormatter: DateFormatter = ({ value, children }) => + children(new Date(value).toDateString()); + +export const UpdatedAtField: FC<{ dateTime?: string; DateFormatterComp?: DateFormatter }> = ({ + dateTime, + DateFormatterComp = DefaultDateFormatter, +}) => { + if (!dateTime) { + return ( + + - + + ); + } + const updatedAt = moment(dateTime); + + if (updatedAt.diff(moment(), 'days') > -7) { + return ( + + {(formattedDate: string) => ( + + {formattedDate} + + )} + + ); + } + return ( + + {updatedAt.format('LL')} + + ); +}; diff --git a/packages/content-management/table_list/src/index.ts b/packages/content-management/table_list/src/index.ts new file mode 100644 index 0000000000000..df0d1e22bc106 --- /dev/null +++ b/packages/content-management/table_list/src/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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { TableListView } from './table_list_view'; + +export type { + Props as TableListViewProps, + State as TableListViewState, + UserContentCommonSchema, +} from './table_list_view'; + +export { TableListViewProvider, TableListViewKibanaProvider } from './services'; diff --git a/packages/content-management/table_list/src/mocks.ts b/packages/content-management/table_list/src/mocks.ts new file mode 100644 index 0000000000000..3c6bb3c68cad1 --- /dev/null +++ b/packages/content-management/table_list/src/mocks.ts @@ -0,0 +1,99 @@ +/* + * Copyright 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 { from } from 'rxjs'; + +import { Services } from './services'; + +/** + * Parameters drawn from the Storybook arguments collection that customize a component story. + */ +export type Params = Record, any>; +type ActionFn = (name: string) => any; + +/** + * Returns Storybook-compatible service abstractions for the `NoDataCard` Provider. + */ +export const getStoryServices = (params: Params, action: ActionFn = () => {}) => { + const services: Services = { + canEditAdvancedSettings: true, + getListingLimitSettingsUrl: () => 'http://elastic.co', + notifyError: (title, text) => { + action('notifyError')({ title, text }); + }, + currentAppId$: from('mockedApp'), + navigateToUrl: () => undefined, + ...params, + }; + + return services; +}; + +/** + * Returns the Storybook arguments for `NoDataCard`, for its stories and for + * consuming component stories. + */ +export const getStoryArgTypes = () => ({ + tableListTitle: { + control: { + type: 'text', + }, + defaultValue: 'My dashboards', + }, + entityName: { + control: { + type: 'text', + }, + defaultValue: 'Dashboard', + }, + entityNamePlural: { + control: { + type: 'text', + }, + defaultValue: 'Dashboards', + }, + canCreateItem: { + control: 'boolean', + defaultValue: true, + }, + canEditItem: { + control: 'boolean', + defaultValue: true, + }, + canDeleteItem: { + control: 'boolean', + defaultValue: true, + }, + showCustomColumn: { + control: 'boolean', + defaultValue: false, + }, + numberOfItemsToRender: { + control: { + type: 'number', + }, + defaultValue: 15, + }, + initialFilter: { + control: { + type: 'text', + }, + defaultValue: '', + }, + initialPageSize: { + control: { + type: 'number', + }, + defaultValue: 10, + }, + listingLimit: { + control: { + type: 'number', + }, + defaultValue: 20, + }, +}); diff --git a/packages/content-management/table_list/src/reducer.tsx b/packages/content-management/table_list/src/reducer.tsx new file mode 100644 index 0000000000000..605e3872c077d --- /dev/null +++ b/packages/content-management/table_list/src/reducer.tsx @@ -0,0 +1,152 @@ +/* + * Copyright 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 { sortBy } from 'lodash'; +import { i18n } from '@kbn/i18n'; + +import { UpdatedAtField } from './components'; +import type { State, UserContentCommonSchema } from './table_list_view'; +import type { Action } from './actions'; +import type { Services } from './services'; + +interface Dependencies { + DateFormatterComp: Services['DateFormatterComp']; +} + +function onInitialItemsFetch( + items: T[], + { DateFormatterComp }: Dependencies +) { + // We check if the saved object have the "updatedAt" metadata + // to render or not that column in the table + const hasUpdatedAtMetadata = Boolean(items.find((item) => Boolean(item.updatedAt))); + + if (hasUpdatedAtMetadata) { + // Add "Last update" column and sort by that column initially + return { + tableSort: { + field: 'updatedAt' as keyof T, + direction: 'desc' as const, + }, + tableColumns: [ + { + field: 'updatedAt', + name: i18n.translate('contentManagement.tableList.lastUpdatedColumnTitle', { + defaultMessage: 'Last updated', + }), + render: (field: string, record: { updatedAt?: string }) => ( + + ), + sortable: true, + width: '150px', + }, + ], + }; + } + + return {}; +} + +export function getReducer({ DateFormatterComp }: Dependencies) { + return (state: State, action: Action): State => { + switch (action.type) { + case 'onFetchItems': { + return { + ...state, + isFetchingItems: true, + }; + } + case 'onFetchItemsSuccess': { + const items = action.data.response.hits; + // We only get the state on the initial fetch of items + // After that we don't want to reset the columns or change the sort after fetching + const { tableColumns, tableSort } = state.hasInitialFetchReturned + ? { tableColumns: undefined, tableSort: undefined } + : onInitialItemsFetch(items, { DateFormatterComp }); + + return { + ...state, + hasInitialFetchReturned: true, + isFetchingItems: false, + items: !state.searchQuery ? sortBy(items, 'title') : items, + totalItems: action.data.response.total, + tableColumns: tableColumns + ? [...state.tableColumns, ...tableColumns] + : state.tableColumns, + tableSort: tableSort ?? state.tableSort, + pagination: { + ...state.pagination, + totalItemCount: items.length, + }, + }; + } + case 'onFetchItemsError': { + return { + ...state, + isFetchingItems: false, + items: [], + totalItems: 0, + fetchError: action.data, + }; + } + case 'onSearchQueryChange': { + return { + ...state, + searchQuery: action.data, + isFetchingItems: true, + }; + } + case 'onTableChange': { + const tableSort = action.data.sort ?? state.tableSort; + return { + ...state, + pagination: { + ...state.pagination, + pageIndex: action.data.page.index, + pageSize: action.data.page.size, + }, + tableSort, + }; + } + case 'showConfirmDeleteItemsModal': { + return { + ...state, + showDeleteModal: true, + }; + } + case 'onDeleteItems': { + return { + ...state, + isDeletingItems: true, + }; + } + case 'onCancelDeleteItems': { + return { + ...state, + showDeleteModal: false, + }; + } + case 'onItemsDeleted': { + return { + ...state, + isDeletingItems: false, + selectedIds: [], + showDeleteModal: false, + }; + } + case 'onSelectionChange': { + return { + ...state, + selectedIds: action.data + .map((item) => item?.id) + .filter((id): id is string => Boolean(id)), + }; + } + } + }; +} diff --git a/packages/content-management/table_list/src/services.tsx b/packages/content-management/table_list/src/services.tsx new file mode 100644 index 0000000000000..ca376c2f83058 --- /dev/null +++ b/packages/content-management/table_list/src/services.tsx @@ -0,0 +1,191 @@ +/* + * Copyright 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, { FC, useContext, useMemo } from 'react'; +import type { EuiTableFieldDataColumnType, SearchFilterConfig } from '@elastic/eui'; +import type { Observable } from 'rxjs'; +import type { FormattedRelative } from '@kbn/i18n-react'; +import { RedirectAppLinksKibanaProvider } from '@kbn/shared-ux-link-redirect-app'; + +import { UserContentCommonSchema } from './table_list_view'; + +type UnmountCallback = () => void; +type MountPoint = (element: HTMLElement) => UnmountCallback; +type NotifyFn = (title: JSX.Element, text?: string) => void; + +export interface SavedObjectsReference { + id: string; + name: string; + type: string; +} + +export type SavedObjectsFindOptionsReference = Omit; + +export type DateFormatter = (props: { + value: number; + children: (formattedDate: string) => JSX.Element; +}) => JSX.Element; + +/** + * Abstract external services for this component. + */ +export interface Services { + canEditAdvancedSettings: boolean; + getListingLimitSettingsUrl: () => string; + notifyError: NotifyFn; + currentAppId$: Observable; + navigateToUrl: (url: string) => Promise | void; + searchQueryParser?: (searchQuery: string) => { + searchQuery: string; + references?: SavedObjectsFindOptionsReference[]; + }; + getTagsColumnDefinition?: () => EuiTableFieldDataColumnType | undefined; + getSearchBarFilters?: () => SearchFilterConfig[]; + DateFormatterComp?: DateFormatter; +} + +const TableListViewContext = React.createContext(null); + +/** + * Abstract external service Provider. + */ +export const TableListViewProvider: FC = ({ children, ...services }) => { + return {children}; +}; + +/** + * Kibana-specific service types. + */ +export interface TableListViewKibanaDependencies { + /** CoreStart contract */ + core: { + application: { + capabilities: { + advancedSettings?: { + save: boolean; + }; + }; + getUrlForApp: (app: string, options: { path: string }) => string; + currentAppId$: Observable; + navigateToUrl: (url: string) => Promise | void; + }; + notifications: { + toasts: { + addDanger: (notifyArgs: { title: MountPoint; text?: string }) => void; + }; + }; + }; + /** + * Handler from the '@kbn/kibana-react-plugin/public' Plugin + * + * ``` + * import { toMountPoint } from '@kbn/kibana-react-plugin/public'; + * ``` + */ + toMountPoint: ( + node: React.ReactNode, + options?: { theme$: Observable<{ readonly darkMode: boolean }> } + ) => MountPoint; + /** + * The public API from the savedObjectsTaggingOss plugin. + * It is returned by calling `getTaggingApi()` from the SavedObjectTaggingOssPluginStart + * + * ```js + * const savedObjectsTagging = savedObjectsTaggingOss?.getTaggingApi() + * ``` + */ + savedObjectsTagging?: { + ui: { + getTableColumnDefinition: () => EuiTableFieldDataColumnType; + parseSearchQuery: ( + query: string, + options?: { + useName?: boolean; + tagField?: string; + } + ) => { + searchTerm: string; + tagReferences: SavedObjectsFindOptionsReference[]; + valid: boolean; + }; + getSearchBarFilter: (options?: { + useName?: boolean; + tagField?: string; + }) => SearchFilterConfig; + }; + }; + /** The component from the @kbn/i18n-react package */ + FormattedRelative: typeof FormattedRelative; +} + +/** + * Kibana-specific Provider that maps to known dependency types. + */ +export const TableListViewKibanaProvider: FC = ({ + children, + ...services +}) => { + const { core, toMountPoint, savedObjectsTagging, FormattedRelative } = services; + + const getSearchBarFilters = useMemo(() => { + if (savedObjectsTagging) { + return () => [savedObjectsTagging.ui.getSearchBarFilter({ useName: true })]; + } + }, [savedObjectsTagging]); + + const searchQueryParser = useMemo(() => { + if (savedObjectsTagging) { + return (searchQuery: string) => { + const res = savedObjectsTagging.ui.parseSearchQuery(searchQuery, { useName: true }); + return { + searchQuery: res.searchTerm, + references: res.tagReferences, + }; + }; + } + }, [savedObjectsTagging]); + + return ( + + + core.application.getUrlForApp('management', { + path: `/kibana/settings?query=savedObjects:listingLimit`, + }) + } + notifyError={(title, text) => { + core.notifications.toasts.addDanger({ title: toMountPoint(title), text }); + }} + getTagsColumnDefinition={savedObjectsTagging?.ui.getTableColumnDefinition} + getSearchBarFilters={getSearchBarFilters} + searchQueryParser={searchQueryParser} + DateFormatterComp={(props) => } + currentAppId$={core.application.currentAppId$} + navigateToUrl={core.application.navigateToUrl} + > + {children} + + + ); +}; + +/** + * React hook for accessing pre-wired services. + */ +export function useServices() { + const context = useContext(TableListViewContext); + + if (!context) { + throw new Error( + 'TableListViewContext is missing. Ensure your component or React root is wrapped with or .' + ); + } + + return context; +} diff --git a/packages/content-management/table_list/src/table_list_view.stories.tsx b/packages/content-management/table_list/src/table_list_view.stories.tsx new file mode 100644 index 0000000000000..7b197c0fa1b5b --- /dev/null +++ b/packages/content-management/table_list/src/table_list_view.stories.tsx @@ -0,0 +1,108 @@ +/* + * Copyright 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 Chance from 'chance'; +import moment from 'moment'; +import { action } from '@storybook/addon-actions'; + +import { Params, getStoryArgTypes, getStoryServices } from './mocks'; + +import { TableListView as Component, UserContentCommonSchema } from './table_list_view'; +import { TableListViewProvider } from './services'; + +import mdx from '../README.mdx'; + +const chance = new Chance(); + +export default { + title: 'Table list view', + description: 'A table list to display user content saved objects', + parameters: { + docs: { + page: mdx, + }, + }, +}; + +const createMockItems = (total: number): UserContentCommonSchema[] => { + return [...Array(total)].map((_, i) => { + const type = itemTypes[Math.floor(Math.random() * 4)]; + + return { + id: i.toString(), + type, + references: [], + updatedAt: moment().subtract(i, 'day').format('YYYY-MM-DDTHH:mm:ss'), + attributes: { + title: chance.sentence({ words: 5 }), + description: `Description of item ${i}`, + }, + }; + }); +}; + +const argTypes = getStoryArgTypes(); +const itemTypes = ['foo', 'bar', 'baz', 'elastic']; +const mockItems: UserContentCommonSchema[] = createMockItems(500); + +export const ConnectedComponent = (params: Params) => { + return ( + + { + const hits = mockItems + .filter((_, i) => i < params.numberOfItemsToRender) + .filter((item) => item.attributes.title.includes(searchQuery)); + + return Promise.resolve({ + total: hits.length, + hits, + }); + }} + getDetailViewLink={() => 'http://elastic.co'} + createItem={ + params.canCreateItem + ? () => { + action('Create item')(); + } + : undefined + } + editItem={ + params.canEditItem + ? ({ attributes: { title } }) => { + action('Edit item')(title); + } + : undefined + } + deleteItems={ + params.canDeleteItem + ? async (items) => { + action('Delete item(s)')( + items.map(({ attributes: { title } }) => title).join(', ') + ); + } + : undefined + } + customTableColumn={ + params.showCustomColumn + ? { + field: 'attributes.type', + name: 'Type', + } + : undefined + } + {...params} + /> + + ); +}; + +ConnectedComponent.argTypes = argTypes; diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.test.tsx b/packages/content-management/table_list/src/table_list_view.test.tsx similarity index 53% rename from src/plugins/kibana_react/public/table_list_view/table_list_view.test.tsx rename to packages/content-management/table_list/src/table_list_view.test.tsx index 0c4d935f524d0..d5c0fda746bfe 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.test.tsx +++ b/packages/content-management/table_list/src/table_list_view.test.tsx @@ -7,13 +7,15 @@ */ import { EuiEmptyPrompt } from '@elastic/eui'; -import { shallowWithIntl, registerTestBed, TestBed } from '@kbn/test-jest-helpers'; -import { ToastsStart } from '@kbn/core/public'; -import React from 'react'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; +import React, { useEffect } from 'react'; import moment, { Moment } from 'moment'; import { act } from 'react-dom/test-utils'; -import { themeServiceMock, applicationServiceMock } from '@kbn/core/public/mocks'; -import { TableListView, TableListViewProps } from './table_list_view'; + +import { WithServices } from './__jest__'; +import { TableListView, Props as TableListViewProps } from './table_list_view'; + +const mockUseEffect = useEffect; jest.mock('lodash', () => { const original = jest.requireActual('lodash'); @@ -24,20 +26,23 @@ jest.mock('lodash', () => { }; }); -const requiredProps: TableListViewProps> = { +jest.mock('react-use/lib/useDebounce', () => { + return (cb: () => void, ms: number, deps: any[]) => { + mockUseEffect(() => { + cb(); + }, deps); + }; +}); + +const requiredProps: TableListViewProps = { entityName: 'test', entityNamePlural: 'tests', listingLimit: 500, initialFilter: '', initialPageSize: 20, - tableColumns: [], tableListTitle: 'test title', - rowHeader: 'name', - tableCaption: 'test caption', - toastNotifications: {} as ToastsStart, - findItems: jest.fn(() => Promise.resolve({ total: 0, hits: [] })), - theme: themeServiceMock.createStartContract(), - application: applicationServiceMock.createStartContract(), + findItems: jest.fn().mockResolvedValue({ total: 0, hits: [] }), + getDetailViewLink: () => 'http://elastic.co', }; describe('TableListView', () => { @@ -49,105 +54,92 @@ describe('TableListView', () => { jest.useRealTimers(); }); + const setup = registerTestBed( + WithServices(TableListView), + { + defaultProps: { ...requiredProps }, + memoryRouter: { wrapComponent: false }, + } + ); + test('render default empty prompt', async () => { - const component = shallowWithIntl(); + let testBed: TestBed; - // Using setState to check the final render while sidestepping the debounced promise management - component.setState({ - hasInitialFetchReturned: true, - isFetchingItems: false, + await act(async () => { + testBed = await setup(); }); - expect(component).toMatchSnapshot(); + const { component, exists } = testBed!; + component.update(); + + expect(component.find(EuiEmptyPrompt).length).toBe(1); + expect(exists('newItemButton')).toBe(false); }); // avoid trapping users in empty prompt that can not create new items test('render default empty prompt with create action when createItem supplied', async () => { - const component = shallowWithIntl( {}} />); + let testBed: TestBed; - // Using setState to check the final render while sidestepping the debounced promise management - component.setState({ - hasInitialFetchReturned: true, - isFetchingItems: false, + await act(async () => { + testBed = await setup({ createItem: () => undefined }); }); - expect(component).toMatchSnapshot(); - }); - - test('render custom empty prompt', () => { - const component = shallowWithIntl( - } /> - ); - - // Using setState to check the final render while sidestepping the debounced promise management - component.setState({ - hasInitialFetchReturned: true, - isFetchingItems: false, - }); + const { component, exists } = testBed!; + component.update(); - expect(component).toMatchSnapshot(); + expect(component.find(EuiEmptyPrompt).length).toBe(1); + expect(exists('newItemButton')).toBe(true); }); - test('render list view', () => { - const component = shallowWithIntl(); + test('render custom empty prompt', async () => { + let testBed: TestBed; + + const CustomEmptyPrompt = () => { + return Table empty} />; + }; - // Using setState to check the final render while sidestepping the debounced promise management - component.setState({ - hasInitialFetchReturned: true, - isFetchingItems: false, - items: [{}], + await act(async () => { + testBed = await setup({ emptyPrompt: }); }); - expect(component).toMatchSnapshot(); + const { component, exists } = testBed!; + component.update(); + + expect(exists('custom-empty-prompt')).toBe(true); }); describe('default columns', () => { - let testBed: TestBed; - - const tableColumns = [ - { - field: 'title', - name: 'Title', - sortable: true, - }, - { - field: 'description', - name: 'Description', - sortable: true, - }, - ]; - const twoDaysAgo = new Date(new Date().setDate(new Date().getDate() - 2)); + const twoDaysAgoToString = new Date(twoDaysAgo.getTime()).toDateString(); const yesterday = new Date(new Date().setDate(new Date().getDate() - 1)); - + const yesterdayToString = new Date(yesterday.getTime()).toDateString(); const hits = [ { - title: 'Item 1', - description: 'Item 1 description', + id: '123', updatedAt: twoDaysAgo, + attributes: { + title: 'Item 1', + description: 'Item 1 description', + }, }, { - title: 'Item 2', - description: 'Item 2 description', + id: '456', // This is the latest updated and should come first in the table updatedAt: yesterday, + attributes: { + title: 'Item 2', + description: 'Item 2 description', + }, }, ]; - const findItems = jest.fn(() => Promise.resolve({ total: hits.length, hits })); - - const defaultProps: TableListViewProps> = { - ...requiredProps, - tableColumns, - findItems, - createItem: () => undefined, - }; - - const setup = registerTestBed(TableListView, { defaultProps }); - test('should add a "Last updated" column if "updatedAt" is provided', async () => { + let testBed: TestBed; + await act(async () => { - testBed = await setup(); + testBed = await setup({ + findItems: jest.fn().mockResolvedValue({ total: hits.length, hits }), + }); }); const { component, table } = testBed!; @@ -156,33 +148,33 @@ describe('TableListView', () => { const { tableCellsValues } = table.getMetaData('itemsInMemTable'); expect(tableCellsValues).toEqual([ - ['Item 2', 'Item 2 description', 'yesterday'], // Comes first as it is the latest updated - ['Item 1', 'Item 1 description', '2 days ago'], + ['Item 2', 'Item 2 description', yesterdayToString], // Comes first as it is the latest updated + ['Item 1', 'Item 1 description', twoDaysAgoToString], ]); }); test('should not display relative time for items updated more than 7 days ago', async () => { + let testBed: TestBed; + const updatedAtValues: Moment[] = []; - const updatedHits = hits.map(({ title, description }, i) => { + const updatedHits = hits.map(({ id, attributes }, i) => { const updatedAt = new Date(new Date().setDate(new Date().getDate() - (7 + i))); - updatedAtValues[i] = moment(updatedAt); + updatedAtValues.push(moment(updatedAt)); return { - title, - description, + id, updatedAt, + attributes, }; }); await act(async () => { testBed = await setup({ - findItems: jest.fn(() => - Promise.resolve({ - total: updatedHits.length, - hits: updatedHits, - }) - ), + findItems: jest.fn().mockResolvedValue({ + total: updatedHits.length, + hits: updatedHits, + }), }); }); @@ -192,21 +184,22 @@ describe('TableListView', () => { const { tableCellsValues } = table.getMetaData('itemsInMemTable'); expect(tableCellsValues).toEqual([ - // Renders the datetime with this format: "05/10/2022 @ 2:34 PM" + // Renders the datetime with this format: "July 28, 2022" ['Item 1', 'Item 1 description', updatedAtValues[0].format('LL')], ['Item 2', 'Item 2 description', updatedAtValues[1].format('LL')], ]); }); test('should not add a "Last updated" column if no "updatedAt" is provided', async () => { + let testBed: TestBed; + await act(async () => { testBed = await setup({ - findItems: jest.fn(() => - Promise.resolve({ - total: hits.length, - hits: hits.map(({ title, description }) => ({ title, description })), - }) - ), + findItems: jest.fn().mockResolvedValue({ + total: hits.length, + // Not including the "updatedAt" metadata + hits: hits.map(({ attributes }) => ({ attributes })), + }), }); }); @@ -222,14 +215,17 @@ describe('TableListView', () => { }); test('should not display anything if there is no updatedAt metadata for an item', async () => { + let testBed: TestBed; + await act(async () => { testBed = await setup({ - findItems: jest.fn(() => - Promise.resolve({ - total: hits.length + 1, - hits: [...hits, { title: 'Item 3', description: 'Item 3 description' }], - }) - ), + findItems: jest.fn().mockResolvedValue({ + total: hits.length + 1, + hits: [ + ...hits, + { id: '789', attributes: { title: 'Item 3', description: 'Item 3 description' } }, + ], + }), }); }); @@ -239,46 +235,33 @@ describe('TableListView', () => { const { tableCellsValues } = table.getMetaData('itemsInMemTable'); expect(tableCellsValues).toEqual([ - ['Item 2', 'Item 2 description', 'yesterday'], - ['Item 1', 'Item 1 description', '2 days ago'], + ['Item 2', 'Item 2 description', yesterdayToString], + ['Item 1', 'Item 1 description', twoDaysAgoToString], ['Item 3', 'Item 3 description', '-'], // Empty column as no updatedAt provided ]); }); }); describe('pagination', () => { - let testBed: TestBed; - - const tableColumns = [ - { - field: 'title', - name: 'Title', - sortable: true, - }, - ]; - const initialPageSize = 20; const totalItems = 30; - const hits = new Array(totalItems).fill(' ').map((_, i) => ({ - title: `Item ${i < 10 ? `0${i}` : i}`, // prefix with "0" for correct A-Z sorting + const hits = [...Array(totalItems)].map((_, i) => ({ + attributes: { + title: `Item ${i < 10 ? `0${i}` : i}`, // prefix with "0" for correct A-Z sorting + }, })); - const findItems = jest.fn().mockResolvedValue({ total: hits.length, hits }); - - const defaultProps: TableListViewProps> = { - ...requiredProps, + const props = { initialPageSize, - tableColumns, - findItems, - createItem: () => undefined, + findItems: jest.fn().mockResolvedValue({ total: hits.length, hits }), }; - const setup = registerTestBed(TableListView, { defaultProps }); - test('should limit the number of row to the `initialPageSize` provided', async () => { + let testBed: TestBed; + await act(async () => { - testBed = await setup(); + testBed = await setup(props); }); const { component, table } = testBed!; @@ -295,8 +278,10 @@ describe('TableListView', () => { }); test('should navigate to page 2', async () => { + let testBed: TestBed; + await act(async () => { - testBed = await setup(); + testBed = await setup(props); }); const { component, table } = testBed!; diff --git a/packages/content-management/table_list/src/table_list_view.tsx b/packages/content-management/table_list/src/table_list_view.tsx new file mode 100644 index 0000000000000..afa41885052e1 --- /dev/null +++ b/packages/content-management/table_list/src/table_list_view.tsx @@ -0,0 +1,526 @@ +/* + * Copyright 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, { + useReducer, + useCallback, + useEffect, + useRef, + useMemo, + ReactNode, + MouseEvent, +} from 'react'; +import useDebounce from 'react-use/lib/useDebounce'; +import { + EuiBasicTableColumn, + EuiButton, + EuiCallOut, + EuiEmptyPrompt, + Pagination, + Direction, + EuiSpacer, + EuiTableActionsColumnType, + EuiLink, +} from '@elastic/eui'; +import { keyBy, uniq, get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; +import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; + +import { Table, ConfirmDeleteModal, ListingLimitWarning } from './components'; +import { useServices } from './services'; +import type { SavedObjectsReference, SavedObjectsFindOptionsReference } from './services'; +import type { Action } from './actions'; +import { getReducer } from './reducer'; + +export interface Props { + entityName: string; + entityNamePlural: string; + tableListTitle: string; + listingLimit: number; + initialFilter: string; + initialPageSize: number; + emptyPrompt?: JSX.Element; + /** Add an additional custom column */ + customTableColumn?: EuiBasicTableColumn; + /** + * Id of the heading element describing the table. This id will be used as `aria-labelledby` of the wrapper element. + * If the table is not empty, this component renders its own h1 element using the same id. + */ + headingId?: string; + /** An optional id for the listing. Used to generate unique data-test-subj. Default: "userContent" */ + id?: string; + children?: ReactNode | undefined; + findItems( + searchQuery: string, + references?: SavedObjectsFindOptionsReference[] + ): Promise<{ total: number; hits: T[] }>; + /** Handler to set the item title "href" value. If it returns undefined there won't be a link for this item. */ + getDetailViewLink?: (entity: T) => string | undefined; + /** Handler to execute when clicking the item title */ + onClickTitle?: (item: T) => void; + createItem?(): void; + deleteItems?(items: T[]): Promise; + editItem?(item: T): void; +} + +export interface State { + items: T[]; + hasInitialFetchReturned: boolean; + isFetchingItems: boolean; + isDeletingItems: boolean; + showDeleteModal: boolean; + fetchError?: IHttpFetchError; + searchQuery: string; + selectedIds: string[]; + totalItems: number; + tableColumns: Array>; + pagination: Pagination; + tableSort?: { + field: keyof T; + direction: Direction; + }; +} + +export interface UserContentCommonSchema { + id: string; + updatedAt: string; + references: SavedObjectsReference[]; + type: string; + attributes: { + title: string; + description?: string; + }; +} + +function TableListViewComp({ + tableListTitle, + entityName, + entityNamePlural, + initialFilter: initialQuery, + headingId, + initialPageSize, + listingLimit, + customTableColumn, + emptyPrompt, + findItems, + createItem, + editItem, + deleteItems, + getDetailViewLink, + onClickTitle, + id = 'userContent', + children, +}: Props) { + if (!getDetailViewLink && !onClickTitle) { + throw new Error( + `[TableListView] One o["getDetailViewLink" or "onClickTitle"] prop must be provided.` + ); + } + + if (getDetailViewLink && onClickTitle) { + throw new Error( + `[TableListView] Either "getDetailViewLink" or "onClickTitle" can be provided. Not both.` + ); + } + + const isMounted = useRef(false); + const fetchIdx = useRef(0); + + const { + canEditAdvancedSettings, + getListingLimitSettingsUrl, + getTagsColumnDefinition, + searchQueryParser, + notifyError, + DateFormatterComp, + navigateToUrl, + currentAppId$, + } = useServices(); + + const reducer = useMemo(() => { + return getReducer({ DateFormatterComp }); + }, [DateFormatterComp]); + + const redirectAppLinksCoreStart = useMemo( + () => ({ + application: { + navigateToUrl, + currentAppId$, + }, + }), + [navigateToUrl, currentAppId$] + ); + + const [state, dispatch] = useReducer<(state: State, action: Action) => State>(reducer, { + items: [], + totalItems: 0, + hasInitialFetchReturned: false, + isFetchingItems: false, + isDeletingItems: false, + showDeleteModal: false, + selectedIds: [], + tableColumns: [ + { + field: 'attributes.title', + name: i18n.translate('contentManagement.tableList.titleColumnName', { + defaultMessage: 'Title', + }), + sortable: true, + render: (field: keyof T, record: T) => { + // The validation is handled at the top of the component + const href = getDetailViewLink ? getDetailViewLink(record) : undefined; + + if (!href && !onClickTitle) { + // This item is not clickable + return {record.attributes.title}; + } + + return ( + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + { + e.preventDefault(); + onClickTitle(record); + } + : undefined + } + data-test-subj={`${id}ListingTitleLink-${record.attributes.title + .split(' ') + .join('-')}`} + > + {record.attributes.title} + + + ); + }, + }, + { + field: 'attributes.description', + name: i18n.translate('contentManagement.tableList.descriptionColumnName', { + defaultMessage: 'Description', + }), + }, + ], + searchQuery: initialQuery, + pagination: { + pageIndex: 0, + totalItemCount: 0, + pageSize: initialPageSize, + pageSizeOptions: uniq([10, 20, 50, initialPageSize]).sort(), + }, + }); + + const { + searchQuery, + hasInitialFetchReturned, + isFetchingItems, + items, + fetchError, + showDeleteModal, + isDeletingItems, + selectedIds, + totalItems, + tableColumns: stateTableColumns, + pagination, + tableSort, + } = state; + const hasNoItems = !isFetchingItems && items.length === 0 && !searchQuery; + const pageDataTestSubject = `${entityName}LandingPage`; + const showFetchError = Boolean(fetchError); + const showLimitError = !showFetchError && totalItems > listingLimit; + + const tableColumns = useMemo(() => { + const columns = stateTableColumns.slice(); + + if (customTableColumn) { + columns.push(customTableColumn); + } + + const tagsColumnDef = getTagsColumnDefinition ? getTagsColumnDefinition() : undefined; + if (tagsColumnDef) { + columns.push(tagsColumnDef); + } + + // Add "Actions" column + if (editItem) { + const actions: EuiTableActionsColumnType['actions'] = [ + { + name: (item) => { + return i18n.translate('contentManagement.tableList.listing.table.editActionName', { + defaultMessage: 'Edit {itemDescription}', + values: { + itemDescription: get(item, 'attributes.title'), + }, + }); + }, + description: i18n.translate( + 'contentManagement.tableList.listing.table.editActionDescription', + { + defaultMessage: 'Edit', + } + ), + icon: 'pencil', + type: 'icon', + enabled: (v) => !(v as unknown as { error: string })?.error, + onClick: editItem, + }, + ]; + + columns.push({ + name: i18n.translate('contentManagement.tableList.listing.table.actionTitle', { + defaultMessage: 'Actions', + }), + width: '100px', + actions, + }); + } + + return columns; + }, [stateTableColumns, customTableColumn, getTagsColumnDefinition, editItem]); + + const itemsById = useMemo(() => { + return keyBy(items, 'id'); + }, [items]); + + const selectedItems = useMemo(() => { + return selectedIds.map((selectedId) => itemsById[selectedId]); + }, [selectedIds, itemsById]); + + // ------------ + // Callbacks + // ------------ + const fetchItems = useCallback(async () => { + dispatch({ type: 'onFetchItems' }); + + try { + const idx = ++fetchIdx.current; + + const { searchQuery: searchQueryParsed, references } = searchQueryParser + ? searchQueryParser(searchQuery) + : { searchQuery, references: undefined }; + + const response = await findItems(searchQueryParsed, references); + + if (!isMounted.current) { + return; + } + + if (idx === fetchIdx.current) { + dispatch({ + type: 'onFetchItemsSuccess', + data: { + response, + }, + }); + } + } catch (err) { + dispatch({ + type: 'onFetchItemsError', + data: err, + }); + } + }, [searchQueryParser, searchQuery, findItems]); + + const deleteSelectedItems = useCallback(async () => { + if (isDeletingItems) { + return; + } + + dispatch({ type: 'onDeleteItems' }); + + try { + await deleteItems!(selectedItems); + } catch (error) { + notifyError( + , + error + ); + } + + fetchItems(); + + dispatch({ type: 'onItemsDeleted' }); + }, [deleteItems, entityName, fetchItems, isDeletingItems, notifyError, selectedItems]); + + const renderCreateButton = useCallback(() => { + if (createItem) { + return ( + + + + ); + } + }, [createItem, entityName]); + + const renderNoItemsMessage = useCallback(() => { + if (emptyPrompt) { + return emptyPrompt; + } else { + return ( + + { + + } + + } + actions={renderCreateButton()} + /> + ); + } + }, [emptyPrompt, entityNamePlural, renderCreateButton]); + + const renderFetchError = useCallback(() => { + return ( + + + } + color="danger" + iconType="alert" + > +

+ +

+
+ +
+ ); + }, [entityName, fetchError]); + + // ------------ + // Effects + // ------------ + useDebounce(fetchItems, 300, [fetchItems]); + + useEffect(() => { + isMounted.current = true; + + return () => { + isMounted.current = false; + }; + }, []); + + // ------------ + // Render + // ------------ + if (!hasInitialFetchReturned) { + return null; + } + + if (!fetchError && hasNoItems) { + return ( + + + {renderNoItemsMessage()} + + + ); + } + + return ( + + {tableListTitle}} + rightSideItems={[renderCreateButton() ?? ]} + data-test-subj="top-nav" + /> + + {/* Any children passed to the component */} + {children} + + {/* Too many items error */} + {showLimitError && ( + + )} + + {/* Error while fetching items */} + {showFetchError && renderFetchError()} + + {/* Table of items */} + + dispatch={dispatch} + items={items} + isFetchingItems={isFetchingItems} + searchQuery={searchQuery} + tableColumns={tableColumns} + tableSort={tableSort} + pagination={pagination} + selectedIds={selectedIds} + entityName={entityName} + entityNamePlural={entityNamePlural} + deleteItems={deleteItems} + tableCaption={tableListTitle} + /> + + {/* Delete modal */} + {showDeleteModal && ( + + isDeletingItems={isDeletingItems} + entityName={entityName} + entityNamePlural={entityNamePlural} + items={selectedItems} + onConfirm={deleteSelectedItems} + onCancel={() => dispatch({ type: 'onCancelDeleteItems' })} + /> + )} + + + ); +} + +const TableListView = React.memo(TableListViewComp) as typeof TableListViewComp; + +export { TableListView }; + +// eslint-disable-next-line import/no-default-export +export default TableListView; diff --git a/packages/content-management/table_list/tsconfig.json b/packages/content-management/table_list/tsconfig.json new file mode 100644 index 0000000000000..f9da39a3d7eb9 --- /dev/null +++ b/packages/content-management/table_list/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node", + "react", + "@kbn/ambient-ui-types", + "@kbn/ambient-storybook-types" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ] +} diff --git a/packages/core/fatal-errors/core-fatal-errors-browser-internal/src/fatal_errors_screen.test.tsx b/packages/core/fatal-errors/core-fatal-errors-browser-internal/src/fatal_errors_screen.test.tsx index 1849517c8cf3f..5fea6d6a66342 100644 --- a/packages/core/fatal-errors/core-fatal-errors-browser-internal/src/fatal_errors_screen.test.tsx +++ b/packages/core/fatal-errors/core-fatal-errors-browser-internal/src/fatal_errors_screen.test.tsx @@ -7,7 +7,7 @@ */ import { EuiCallOut } from '@elastic/eui'; -import testSubjSelector from '@kbn/test-subj-selector'; +import { subj as testSubjSelector } from '@kbn/test-subj-selector'; import React from 'react'; import { of, ReplaySubject } from 'rxjs'; import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; diff --git a/packages/kbn-babel-preset/node_preset.js b/packages/kbn-babel-preset/node_preset.js index 5b50e91354988..caf5f7dce419e 100644 --- a/packages/kbn-babel-preset/node_preset.js +++ b/packages/kbn-babel-preset/node_preset.js @@ -31,7 +31,7 @@ module.exports = (_, options = {}) => { // Because of that we should use for that value the same version we install // in the package.json in order to have the same polyfills between the environment // and the tests - corejs: '3.25.0', + corejs: '3.25.1', bugfixes: true, ...(options['@babel/preset-env'] || {}), diff --git a/packages/kbn-babel-preset/webpack_preset.js b/packages/kbn-babel-preset/webpack_preset.js index e3b52cc1e5e11..5d2fa5a627fb6 100644 --- a/packages/kbn-babel-preset/webpack_preset.js +++ b/packages/kbn-babel-preset/webpack_preset.js @@ -18,7 +18,7 @@ module.exports = (_, options = {}) => { modules: false, // Please read the explanation for this // in node_preset.js - corejs: '3.25.0', + corejs: '3.25.1', bugfixes: true, }, ], diff --git a/packages/kbn-bazel-packages/src/bazel_package_dirs.js b/packages/kbn-bazel-packages/src/bazel_package_dirs.js index 7e3f728a21ca7..dbb8826bcd733 100644 --- a/packages/kbn-bazel-packages/src/bazel_package_dirs.js +++ b/packages/kbn-bazel-packages/src/bazel_package_dirs.js @@ -26,6 +26,7 @@ const BAZEL_PACKAGE_DIRS = [ 'packages/analytics/shippers/elastic_v3', 'packages/core/*', 'packages/home', + 'packages/content-management', 'x-pack/packages/ml', ]; diff --git a/packages/kbn-es-query/src/es_query/types.ts b/packages/kbn-es-query/src/es_query/types.ts index 14e091ed5b7f2..89c22521a6409 100644 --- a/packages/kbn-es-query/src/es_query/types.ts +++ b/packages/kbn-es-query/src/es_query/types.ts @@ -54,6 +54,10 @@ export type DataViewFieldBase = { */ lang?: estypes.ScriptLanguage; scripted?: boolean; + /** + * ES field types as strings array. + */ + esTypes?: string[]; }; /** diff --git a/packages/kbn-es-query/src/filters/stubs/fields.mocks.ts b/packages/kbn-es-query/src/filters/stubs/fields.mocks.ts index 2d9b7269f85a4..390d322ffb9ed 100644 --- a/packages/kbn-es-query/src/filters/stubs/fields.mocks.ts +++ b/packages/kbn-es-query/src/filters/stubs/fields.mocks.ts @@ -42,6 +42,12 @@ export const fields: DataViewFieldBase[] = [ type: 'string', scripted: false, }, + { + name: 'machine.os.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + }, { name: 'script number', type: 'number', diff --git a/packages/kbn-es-query/src/kuery/functions/is.test.ts b/packages/kbn-es-query/src/kuery/functions/is.test.ts index f0143db87809b..f36ea0d17dc8c 100644 --- a/packages/kbn-es-query/src/kuery/functions/is.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/is.test.ts @@ -206,6 +206,25 @@ describe('kuery functions', () => { expect(result).toEqual(expected); }); + test('should create a wildcard query for keyword fields', () => { + const expected = { + bool: { + should: [ + { + wildcard: { + 'machine.os.keyword': 'win*', + }, + }, + ], + minimum_should_match: 1, + }, + }; + const node = nodeTypes.function.buildNode('is', 'machine.os.keyword', 'win*'); + const result = is.toElasticsearchQuery(node, indexPattern); + + expect(result).toEqual(expected); + }); + test('should support scripted fields', () => { const node = nodeTypes.function.buildNode('is', 'script string', 'foo'); const result = is.toElasticsearchQuery(node, indexPattern); diff --git a/packages/kbn-es-query/src/kuery/functions/is.ts b/packages/kbn-es-query/src/kuery/functions/is.ts index 12ada3326604d..f2db74857b02e 100644 --- a/packages/kbn-es-query/src/kuery/functions/is.ts +++ b/packages/kbn-es-query/src/kuery/functions/is.ts @@ -142,15 +142,20 @@ export function toElasticsearchQuery( }), ]; } else if (wildcard.isNode(valueArg)) { - return [ - ...accumulator, - wrapWithNestedQuery({ - query_string: { - fields: [field.name], - query: wildcard.toQueryStringQuery(valueArg), - }, - }), - ]; + const query = field.esTypes?.includes('keyword') + ? { + wildcard: { + [field.name]: value, + }, + } + : { + query_string: { + fields: [field.name], + query: wildcard.toQueryStringQuery(valueArg), + }, + }; + + return [...accumulator, wrapWithNestedQuery(query)]; } else if (field.type === 'date') { /* If we detect that it's a date field and the user wants an exact date, we need to convert the query to both >= and <= the value provided to force a range query. This is because match and match_phrase queries do not accept a timezone parameter. diff --git a/packages/kbn-es-query/src/kuery/functions/utils/get_fields.test.ts b/packages/kbn-es-query/src/kuery/functions/utils/get_fields.test.ts index f3d49c426d079..e0e69c222604e 100644 --- a/packages/kbn-es-query/src/kuery/functions/utils/get_fields.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/utils/get_fields.test.ts @@ -74,10 +74,13 @@ describe('getFields', () => { const fieldNameNode = nodeTypes.wildcard.buildNode('machine*'); const results = getFields(fieldNameNode, indexPattern); - expect(Array.isArray(results)).toBeTruthy(); - expect(results).toHaveLength(2); - expect(results!.find((field) => field.name === 'machine.os')).toBeDefined(); - expect(results!.find((field) => field.name === 'machine.os.raw')).toBeDefined(); + expect(results).toEqual(expect.any(Array)); + expect(results).toHaveLength(3); + expect(results).toEqual([ + expect.objectContaining({ name: 'machine.os' }), + expect.objectContaining({ name: 'machine.os.raw' }), + expect.objectContaining({ name: 'machine.os.keyword' }), + ]); }); }); }); diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index b05f1adf1727a..35231e4e4d0ae 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -31,7 +31,7 @@ pageLoadAssetSize: embeddableEnhanced: 22107 enterpriseSearch: 35741 esUiShared: 326654 - eventAnnotation: 19334 + eventAnnotation: 19500 expressionError: 22127 expressionGauge: 25000 expressionHeatmap: 27505 @@ -86,6 +86,7 @@ pageLoadAssetSize: osquery: 107090 painlessLab: 179748 presentationUtil: 58834 + profiling: 18628 remoteClusters: 51327 reporting: 57003 rollup: 97204 diff --git a/packages/kbn-test-jest-helpers/src/testbed/testbed.ts b/packages/kbn-test-jest-helpers/src/testbed/testbed.ts index ddd574ace64b8..a05133b35f581 100644 --- a/packages/kbn-test-jest-helpers/src/testbed/testbed.ts +++ b/packages/kbn-test-jest-helpers/src/testbed/testbed.ts @@ -55,18 +55,18 @@ const defaultConfig: TestBedConfig = { }); ``` */ -export function registerTestBed( +export function registerTestBed( Component: ComponentType, config: AsyncTestBedConfig -): AsyncSetupFunc; -export function registerTestBed( +): AsyncSetupFunc>; +export function registerTestBed( Component: ComponentType, config?: TestBedConfig -): SyncSetupFunc; -export function registerTestBed( +): SyncSetupFunc>; +export function registerTestBed( Component: ComponentType, config?: AsyncTestBedConfig | TestBedConfig -): SetupFunc { +): SetupFunc> { const { defaultProps = defaultConfig.defaultProps, memoryRouter = defaultConfig.memoryRouter!, @@ -263,7 +263,7 @@ export function registerTestBed( .slice(1) // we remove the first row as it is the table header .map((row) => ({ reactWrapper: row, - columns: row.find('td').map((col) => ({ + columns: row.find('.euiTableCellContent').map((col) => ({ reactWrapper: col, // We can't access the td value with col.text() because // eui adds an extra div in td on mobile => (.euiTableRowCell__mobileHeader) diff --git a/packages/kbn-test-jest-helpers/src/testbed/types.ts b/packages/kbn-test-jest-helpers/src/testbed/types.ts index 15996646ec80a..6390be14be00e 100644 --- a/packages/kbn-test-jest-helpers/src/testbed/types.ts +++ b/packages/kbn-test-jest-helpers/src/testbed/types.ts @@ -10,9 +10,9 @@ import { Store } from 'redux'; import { ReactWrapper as GenericReactWrapper } from 'enzyme'; import { LocationDescriptor } from 'history'; -export type AsyncSetupFunc = (props?: any) => Promise>; -export type SyncSetupFunc = (props?: any) => TestBed; -export type SetupFunc = (props?: any) => TestBed | Promise>; +export type AsyncSetupFunc = (props?: P) => Promise>; +export type SyncSetupFunc = (props?: P) => TestBed; +export type SetupFunc = (props?: P) => TestBed | Promise>; export type ReactWrapper = GenericReactWrapper; export interface EuiTableMetaData { diff --git a/packages/kbn-test-subj-selector/BUILD.bazel b/packages/kbn-test-subj-selector/BUILD.bazel index cc3334650a5d9..a92554c948230 100644 --- a/packages/kbn-test-subj-selector/BUILD.bazel +++ b/packages/kbn-test-subj-selector/BUILD.bazel @@ -1,13 +1,28 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") -load("//src/dev/bazel:index.bzl", "pkg_npm") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-test-subj-selector" +PKG_DIRNAME = "kbn-test-subj-selector" PKG_REQUIRE_NAME = "@kbn/test-subj-selector" -SOURCE_FILES = glob([ - "index.d.ts", - "index.js" -]) +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) SRCS = SOURCE_FILES @@ -20,29 +35,90 @@ NPM_MODULE_EXTRA_FILES = [ "package.json", ] -RUNTIME_DEPS = [] +# In this array place runtime dependencies, including other packages and NPM packages +# which must be available for this code to run. +# +# To reference other packages use: +# "//repo/relative/path/to/package" +# eg. "//packages/kbn-utils" +# +# To reference a NPM package use: +# "@npm//name-of-package" +# eg. "@npm//lodash" +RUNTIME_DEPS = [ +] -js_library( - name = PKG_BASE_NAME, - srcs = NPM_MODULE_EXTRA_FILES + [ - ":srcs", +# In this array place dependencies necessary to build the types, which will include the +# :npm_module_types target of other packages and packages from NPM, including @types/* +# packages. +# +# To reference the types for another package use: +# "//repo/relative/path/to/package:npm_module_types" +# eg. "//packages/kbn-utils:npm_module_types" +# +# References to NPM packages work the same as RUNTIME_DEPS +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", ], - deps = RUNTIME_DEPS, +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( name = "build", - srcs = [ - ":npm_module", - ], + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], visibility = ["//visibility:public"], ) diff --git a/packages/kbn-test-subj-selector/README.mdx b/packages/kbn-test-subj-selector/README.mdx index c924d15937129..9f36c3c63670c 100755 --- a/packages/kbn-test-subj-selector/README.mdx +++ b/packages/kbn-test-subj-selector/README.mdx @@ -7,4 +7,20 @@ date: 2022-05-19 tags: ['kibana', 'dev', 'contributor', 'operations', 'test', 'subj', 'selector'] --- -Converts a string from a test subject syntax into a css selectors composed by `data-test-subj`. +Converts a testSubject selector into a CSS selector. + +testSubject selector syntax rules: + + - `data-test-subj` values can include spaces + + - prefixing a value with `~` will allow matching a single word in a `data-test-subj` that uses several space delimited list words + - example: `~foo` + - css equivalent: `[data-test-subj~="foo"]` + + - the `>` character is used between two values to indicate that the value on the right must match an element inside an element matched by the value on the left + - example: `foo > bar` + - css equivalent: `[data-test-subj=foo] [data-test-subj=bar]` + + - the `&` character is used between two values to indicate that the value on both sides must both match the element + - example: `foo & bar` + - css equivalent: `[data-test-subj=foo][data-test-subj=bar]` \ No newline at end of file diff --git a/packages/kbn-test-subj-selector/index.js b/packages/kbn-test-subj-selector/index.js deleted file mode 100755 index 0163983d87eea..0000000000000 --- a/packages/kbn-test-subj-selector/index.js +++ /dev/null @@ -1,38 +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. - */ - -function selectorToTerms(selector) { - return selector - .replace(/\s*~\s*/g, '~') // css locator with '~' operator cannot contain spaces - .replace(/\s*>\s*/g, '>') // remove all whitespace around joins > - .replace(/\s*&\s*/g, '&') // remove all whitespace around joins & - .split(/>+/); -} - -function termToCssSelector(term) { - if (term) { - return term.startsWith('~') - ? '[data-test-subj~="' + term.substring(1).replace(/\s/g, '') + '"]' - : '[data-test-subj="' + term + '"]'; - } else { - return ''; - } -} - -module.exports = function testSubjSelector(selector) { - const cssSelectors = []; - const terms = selectorToTerms(selector); - - while (terms.length) { - const term = terms.shift(); - // split each term by joins/& and map to css selectors - cssSelectors.push(term.split('&').map(termToCssSelector).join('')); - } - - return cssSelectors.join(' '); -}; diff --git a/packages/kbn-test-subj-selector/index.test.js b/packages/kbn-test-subj-selector/index.test.js deleted file mode 100755 index aa23c8d1bd70b..0000000000000 --- a/packages/kbn-test-subj-selector/index.test.js +++ /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 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. - */ - -const testSubjSelector = require('.'); - -describe('testSubjSelector()', function () { - it('converts subjectSelectors to cssSelectors', function () { - expect(testSubjSelector('foo bar')).toEqual('[data-test-subj="foo bar"]'); - expect(testSubjSelector('foo > bar')).toEqual('[data-test-subj="foo"] [data-test-subj="bar"]'); - expect(testSubjSelector('foo > bar baz')).toEqual( - '[data-test-subj="foo"] [data-test-subj="bar baz"]' - ); - expect(testSubjSelector('foo> ~bar')).toEqual('[data-test-subj="foo"] [data-test-subj~="bar"]'); - expect(testSubjSelector('~ foo')).toEqual('[data-test-subj~="foo"]'); - expect(testSubjSelector('~foo & ~ bar')).toEqual( - '[data-test-subj~="foo"][data-test-subj~="bar"]' - ); - }); -}); diff --git a/src/plugins/kibana_react/public/table_list_view/index.ts b/packages/kbn-test-subj-selector/index.ts similarity index 88% rename from src/plugins/kibana_react/public/table_list_view/index.ts rename to packages/kbn-test-subj-selector/index.ts index 8fdc5a27df01c..d815909b39b7b 100644 --- a/src/plugins/kibana_react/public/table_list_view/index.ts +++ b/packages/kbn-test-subj-selector/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export * from './table_list_view'; +export { subj } from './test_subj_selector'; diff --git a/packages/kbn-test-subj-selector/jest.config.js b/packages/kbn-test-subj-selector/jest.config.js index 8395fe2837911..26b71c41970d1 100644 --- a/packages/kbn-test-subj-selector/jest.config.js +++ b/packages/kbn-test-subj-selector/jest.config.js @@ -7,7 +7,7 @@ */ module.exports = { - preset: '@kbn/test', + preset: '@kbn/test/jest_node', rootDir: '../..', roots: ['/packages/kbn-test-subj-selector'], }; diff --git a/packages/kbn-test-subj-selector/kibana.jsonc b/packages/kbn-test-subj-selector/kibana.jsonc index 697ce5593ea71..708e1fd44ac39 100644 --- a/packages/kbn-test-subj-selector/kibana.jsonc +++ b/packages/kbn-test-subj-selector/kibana.jsonc @@ -1,8 +1,8 @@ { "type": "shared-common", "id": "@kbn/test-subj-selector", - "devOnly": true, "owner": "@elastic/kibana-operations", + "devOnly": true, "runtimeDeps": [], - "typeDeps": [] + "typeDeps": [], } diff --git a/packages/kbn-test-subj-selector/package.json b/packages/kbn-test-subj-selector/package.json old mode 100755 new mode 100644 index 8ced36990a6e3..6c8d040735d50 --- a/packages/kbn-test-subj-selector/package.json +++ b/packages/kbn-test-subj-selector/package.json @@ -1,10 +1,7 @@ { "name": "@kbn/test-subj-selector", - "version": "0.2.1", - "description": "", - "main": "index.js", - "keywords": [], - "author": "Spencer Alger ", - "license": "SSPL-1.0 OR Elastic License 2.0", - "private": "true" + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" } diff --git a/packages/kbn-test-subj-selector/test_subj_selector.test.ts b/packages/kbn-test-subj-selector/test_subj_selector.test.ts new file mode 100644 index 0000000000000..d8ad1e6ddfd56 --- /dev/null +++ b/packages/kbn-test-subj-selector/test_subj_selector.test.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 { subj } from './test_subj_selector'; + +describe('testSubjSelector()', function () { + it('converts subjectSelectors to cssSelectors', function () { + expect(subj('foo bar')).toEqual('[data-test-subj="foo bar"]'); + expect(subj('foo > bar')).toEqual('[data-test-subj="foo"] [data-test-subj="bar"]'); + expect(subj('foo > bar baz')).toEqual('[data-test-subj="foo"] [data-test-subj="bar baz"]'); + expect(subj('foo> ~bar')).toEqual('[data-test-subj="foo"] [data-test-subj~="bar"]'); + expect(subj('~ foo')).toEqual('[data-test-subj~="foo"]'); + expect(subj('~foo & ~ bar')).toEqual('[data-test-subj~="foo"][data-test-subj~="bar"]'); + }); +}); diff --git a/packages/kbn-test-subj-selector/test_subj_selector.ts b/packages/kbn-test-subj-selector/test_subj_selector.ts new file mode 100644 index 0000000000000..e6db49755a89b --- /dev/null +++ b/packages/kbn-test-subj-selector/test_subj_selector.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 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. + */ + +function selectorToTerms(selector: string) { + return selector + .replace(/\s*~\s*/g, '~') // css locator with '~' operator cannot contain spaces + .replace(/\s*>\s*/g, '>') // remove all whitespace around joins > + .replace(/\s*&\s*/g, '&') // remove all whitespace around joins & + .split(/>+/); +} + +function termToCssSelector(term: string) { + if (term) { + return term.startsWith('~') + ? '[data-test-subj~="' + term.substring(1).replace(/\s/g, '') + '"]' + : '[data-test-subj="' + term + '"]'; + } else { + return ''; + } +} + +/** + * Converts a testSubject selector into a CSS selector. + * + * testSubject selector syntax rules: + * + * - `data-test-subj` values can include spaces + * + * - prefixing a value with `~` will allow matching a single word in a `data-test-subj` that uses several space delimited list words + * - example: `~foo` + * - css equivalent: `[data-test-subj~="foo"]` + * + * - the `>` character is used between two values to indicate that the value on the right must match an element inside an element matched by the value on the left + * - example: `foo > bar` + * - css equivalent: `[data-test-subj=foo] [data-test-subj=bar]` + * + * - the `&` character is used between two values to indicate that the value on both sides must both match the element + * - example: `foo & bar` + * - css equivalent: `[data-test-subj=foo][data-test-subj=bar]` + */ +export function subj(selector: string) { + return selectorToTerms(selector) + .map((term) => + // split each term by joins/& and map to css selectors + term.split('&').map(termToCssSelector).join('') + ) + .join(' '); +} diff --git a/packages/kbn-test-subj-selector/tsconfig.json b/packages/kbn-test-subj-selector/tsconfig.json index a590073946162..81935b1385550 100644 --- a/packages/kbn-test-subj-selector/tsconfig.json +++ b/packages/kbn-test-subj-selector/tsconfig.json @@ -1,9 +1,17 @@ { "extends": "../../tsconfig.bazel.json", "compilerOptions": { - "outDir": "target/types" + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] }, "include": [ - "index.d.ts" + "**/*.ts", ] } diff --git a/packages/kbn-typed-react-router-config/src/create_router.test.tsx b/packages/kbn-typed-react-router-config/src/create_router.test.tsx index e0582de320a96..fe4ada218b0b4 100644 --- a/packages/kbn-typed-react-router-config/src/create_router.test.tsx +++ b/packages/kbn-typed-react-router-config/src/create_router.test.tsx @@ -10,6 +10,7 @@ import * as t from 'io-ts'; import { toNumberRt } from '@kbn/io-ts-utils'; import { createRouter } from './create_router'; import { createMemoryHistory } from 'history'; +import { last } from 'lodash'; describe('createRouter', () => { const routes = { @@ -382,4 +383,12 @@ describe('createRouter', () => { ); }); }); + + describe('getRoutePath', () => { + it('returns the correct route path', () => { + expect( + router.getRoutePath(last(router.getRoutesToMatch('/services/opbeans-java/errors'))!) + ).toBe('/services/{serviceName}/errors'); + }); + }); }); diff --git a/packages/kbn-typed-react-router-config/src/create_router.ts b/packages/kbn-typed-react-router-config/src/create_router.ts index f545fa8ed63e3..4430c852b19ed 100644 --- a/packages/kbn-typed-react-router-config/src/create_router.ts +++ b/packages/kbn-typed-react-router-config/src/create_router.ts @@ -208,7 +208,7 @@ export function createRouter(routes: TRoutes): Router< return matchRoutes(...args) as any; }, getRoutePath: (route) => { - return reactRouterConfigsByRoute.get(route)!.path as string; + return route.path; }, getRoutesToMatch: (path: string) => { return getRoutesToMatch(path) as unknown as FlattenRoutesOf; diff --git a/packages/kbn-typed-react-router-config/src/types/index.ts b/packages/kbn-typed-react-router-config/src/types/index.ts index a5121ba0d72c9..f4655c7022db9 100644 --- a/packages/kbn-typed-react-router-config/src/types/index.ts +++ b/packages/kbn-typed-react-router-config/src/types/index.ts @@ -36,7 +36,7 @@ export interface RouteWithPath extends Route { } export interface RouteMatch { - route: TRoute; + route: TRoute & { path: string }; match: { isExact: boolean; path: string; @@ -146,7 +146,7 @@ export interface Router { path: TPath, ...args: TypeAsArgs> ): string; - getRoutePath(route: Route): string; + getRoutePath(route: RouteWithPath): string; getRoutesToMatch(path: string): FlattenRoutesOf; } diff --git a/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.component.tsx b/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.component.tsx index cefe4197c3cde..4a64700b91e70 100644 --- a/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.component.tsx +++ b/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.component.tsx @@ -28,6 +28,7 @@ export const RedirectAppLinks: FC = ({ children, navigateToUrl, currentAppId, + className, }) => { const containerRef = useRef(null); @@ -44,7 +45,7 @@ export const RedirectAppLinks: FC = ({ return ( // eslint-disable-next-line jsx-a11y/click-events-have-key-events -
+
{children}
); diff --git a/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.container.tsx b/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.container.tsx index 9a069881b2128..b572b5605c359 100644 --- a/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.container.tsx +++ b/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.container.tsx @@ -22,6 +22,8 @@ import { RedirectAppLinks as Component } from './redirect_app_links.component'; * * ``` */ -export const RedirectAppLinks: FC<{}> = ({ children }) => ( - {children} +export const RedirectAppLinks: FC<{ className?: string }> = ({ className, children }) => ( + + {children} + ); diff --git a/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.tsx b/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.tsx index 2909dcdbda17d..0b4c5d0877a61 100644 --- a/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.tsx +++ b/packages/shared-ux/link/redirect_app/impl/src/redirect_app_links.tsx @@ -24,8 +24,10 @@ const isKibanaContract = (services: any): services is RedirectAppLinksKibanaDepe * `RedirectAppLinksKibanaProvider` based on the services provided, creating a single component * with which consumers can wrap their components or solutions. */ -export const RedirectAppLinks: FC = ({ children, ...props }) => { - const container = {children}; +export const RedirectAppLinks: FC = ({ children, className, ...props }) => { + const container = ( + {children} + ); if (isKibanaContract(props)) { const { coreStart } = props; diff --git a/packages/shared-ux/link/redirect_app/types/index.d.ts b/packages/shared-ux/link/redirect_app/types/index.d.ts index 186e86af89435..aea7d597b137e 100644 --- a/packages/shared-ux/link/redirect_app/types/index.d.ts +++ b/packages/shared-ux/link/redirect_app/types/index.d.ts @@ -32,7 +32,10 @@ export interface RedirectAppLinksKibanaDependencies { } /** Props for the `RedirectAppLinks` component. */ -export type RedirectAppLinksProps = RedirectAppLinksServices | RedirectAppLinksKibanaDependencies; +export type RedirectAppLinksProps = { className?: string } & ( + | RedirectAppLinksServices + | RedirectAppLinksKibanaDependencies +); /** Props for the `RedirectAppLinksComponent`. */ export interface RedirectAppLinksComponentProps diff --git a/renovate.json b/renovate.json index 8ec367e91ee71..508cdcc684fc2 100644 --- a/renovate.json +++ b/renovate.json @@ -207,6 +207,15 @@ "matchBaseBranches": ["main"], "labels": ["release_note:skip", "backport:skip", "ci:all-cypress-suites"], "enabled": true + }, + { + "groupName": "Profiling", + "matchPackageNames": ["fnv-plus", "peggy", "@types/dagre", "@types/fnv-plus"], + "reviewers": ["team:profiling-ui"], + "matchBaseBranches": ["main"], + "labels": ["release_note:skip", "backport:skip"], + "enabled": true, + "prCreation": "immediate" } ] } diff --git a/src/core/types/elasticsearch/search.ts b/src/core/types/elasticsearch/search.ts index 036f85177d305..1543add4c215c 100644 --- a/src/core/types/elasticsearch/search.ts +++ b/src/core/types/elasticsearch/search.ts @@ -607,6 +607,8 @@ export type InferSearchResponseOf< > = Omit, 'aggregations' | 'hits'> & (TSearchRequest['body'] extends TopLevelAggregationRequest ? WrapAggregationResponse> + : TSearchRequest extends TopLevelAggregationRequest + ? WrapAggregationResponse> : { aggregations?: InvalidAggregationRequest }) & { hits: Omit['hits'], 'total' | 'hits'> & (TOptions['restTotalHitsAsInt'] extends true @@ -618,5 +620,12 @@ export type InferSearchResponseOf< value: number; relation: 'eq' | 'gte'; }; - }) & { hits: HitsOf }; + }) & { + hits: HitsOf< + TSearchRequest['body'] extends estypes.SearchRequest['body'] + ? TSearchRequest['body'] + : TSearchRequest, + TDocument + >; + }; }; diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index 418a8f448b2c8..f79c53e115149 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -62,6 +62,8 @@ export const IGNORE_FILE_GLOBS = [ 'x-pack/plugins/maps/server/fonts/**/*', + 'x-pack/plugins/profiling/Makefile', + // Bazel default files '**/WORKSPACE.bazel', '**/BUILD.bazel', diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index 43afb75e8265f..1705c9ac2ec9c 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -14,6 +14,7 @@ export const storybookAliases = { cloud: 'x-pack/plugins/cloud/.storybook', coloring: 'packages/kbn-coloring/.storybook', chart_icons: 'packages/kbn-chart-icons/.storybook', + content_management: 'packages/content-management/.storybook', controls: 'src/plugins/controls/storybook', custom_integrations: 'src/plugins/custom_integrations/storybook', dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook', diff --git a/src/plugins/advanced_settings/public/index.ts b/src/plugins/advanced_settings/public/index.ts index 58222c19a5172..6d6bf6f055f41 100644 --- a/src/plugins/advanced_settings/public/index.ts +++ b/src/plugins/advanced_settings/public/index.ts @@ -20,6 +20,10 @@ export { ComponentRegistry } from './component_registry'; const LazyField = React.lazy(() => import('./management_app/components/field')); export { LazyField }; +export { toEditableConfig } from './management_app/lib/to_editable_config'; + export function plugin(initializerContext: PluginInitializerContext) { return new AdvancedSettingsPlugin(); } + +export type { FieldState } from './management_app/types'; diff --git a/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx b/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx index ee61e7974157c..16ce112cea699 100644 --- a/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx @@ -22,9 +22,11 @@ import { ExpressionRenderDefinition } from '@kbn/expressions-plugin/common'; import { FormatFactory } from '@kbn/field-formats-plugin/common'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; -import { isDataLayer } from '../../common/utils/layer_types_guards'; +import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils'; + +import type { getDataLayers } from '../helpers'; import { LayerTypes, SeriesTypes } from '../../common/constants'; -import type { CommonXYLayerConfig, XYChartProps } from '../../common'; +import type { XYChartProps } from '../../common'; import type { BrushEvent, FilterEvent } from '../types'; // eslint-disable-next-line @kbn/imports/no_boundary_crossing import { extractContainerType, extractVisualizationType } from '../../../common'; @@ -47,9 +49,17 @@ interface XyChartRendererDeps { getStartDeps: GetStartDepsFn; } -const extractCounterEvents = (originatingApp: string, layers: CommonXYLayerConfig[]) => { - const dataLayer = layers.find(isDataLayer); - if (dataLayer) { +const extractCounterEvents = ( + originatingApp: string, + { layers, yAxisConfigs }: XYChartProps['args'], + services: { + getDataLayers: typeof getDataLayers; + } +) => { + const dataLayers = services.getDataLayers(layers); + + if (dataLayers.length) { + const [dataLayer] = dataLayers; const type = dataLayer.seriesType === SeriesTypes.BAR ? `${dataLayer.isHorizontal ? 'horizontal_bar' : 'vertical_bar'}` @@ -76,11 +86,50 @@ const extractCounterEvents = (originatingApp: string, layers: CommonXYLayerConfi } ); + // Multiple axes configured on the same side of the chart + const multiAxisSide = Object.values( + (yAxisConfigs ?? []).reduce>((acc, item) => { + if (item.position) { + acc[item.position] = (acc[item.position] ?? 0) + 1; + } + return acc; + }, {}) + ).find((i) => i > 1); + + const multiSplitNonTerms = (dataLayer.splitAccessors ?? []) + .map((splitAccessor) => + getColumnByAccessor( + splitAccessor, + dataLayer.table.columns + )?.meta?.sourceParams?.type?.toString() + ) + .filter(Boolean); + + const aggregateLayers: string[] = dataLayers + .map((l) => + l.accessors.reduce((acc, accessor) => { + const metricType = getColumnByAccessor( + accessor, + l.table.columns + )?.meta?.sourceParams?.type?.toString(); + + if ( + metricType && + ['avg_bucket', 'min_bucket', 'max_bucket', 'sum_bucket'].includes(metricType) + ) { + acc.push(metricType); + } + return acc; + }, []) + ) + .flat(); + return [ [ type, dataLayer.isPercentage ? 'percentage' : undefined, dataLayer.isStacked ? 'stacked' : undefined, + // There's a metric configured for the dot size in an area or line chart ] .filter(Boolean) .join('_'), @@ -88,6 +137,18 @@ const extractCounterEvents = (originatingApp: string, layers: CommonXYLayerConfi byTypes[LayerTypes.ANNOTATIONS] ? 'annotation_layer' : undefined, byTypes[LayerTypes.DATA] > 1 ? 'multiple_data_layers' : undefined, byTypes.mixedXY ? 'mixed_xy' : undefined, + dataLayer.markSizeAccessor ? 'metric_dot_size' : undefined, + multiAxisSide ? 'multi_axis_same_side' : undefined, + // There are multiple "split series" aggs in an xy chart and they are not all terms but other aggs + multiSplitNonTerms.length > 1 && !multiSplitNonTerms.every((i) => i === 'terms') + ? 'multi_split_non_terms' + : undefined, + // Multiple average/min/max/sum bucket aggs in a single vis or + // one average/min/max/sum bucket aggs on an xy chart with at least one "split series" defined + aggregateLayers.length > 1 || + (aggregateLayers.length === 1 && dataLayer.splitAccessors?.length) + ? 'aggregate_bucket' + : undefined, ] .filter(Boolean) .map((item) => `render_${originatingApp}_${item}`); @@ -107,6 +168,12 @@ export const getXyChartRenderer = ({ render: async (domNode: Element, config: XYChartProps, handlers) => { const deps = await getStartDeps(); + // Lazy loaded parts + const [{ XYChartReportable }, { calculateMinInterval, getDataLayers }] = await Promise.all([ + import('../components/xy_chart'), + import('../helpers'), + ]); + handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); const onClickValue = (data: FilterEvent['data']) => { handlers.event({ name: 'filter', data }); @@ -121,7 +188,9 @@ export const getXyChartRenderer = ({ const visualizationType = extractVisualizationType(executionContext); if (deps.usageCollection && containerType && visualizationType) { - const uiEvents = extractCounterEvents(visualizationType, config.args.layers); + const uiEvents = extractCounterEvents(visualizationType, config.args, { + getDataLayers, + }); if (uiEvents) { deps.usageCollection.reportUiCounter(containerType, METRIC_TYPE.COUNT, uiEvents); @@ -131,11 +200,6 @@ export const getXyChartRenderer = ({ handlers.done(); }; - const [{ XYChartReportable }, { calculateMinInterval }] = await Promise.all([ - import('../components/xy_chart'), - import('../helpers/interval'), - ]); - const chartContainerStyle = css({ position: 'relative', width: '100%', diff --git a/src/plugins/console/public/application/models/legacy_core_editor/mode/output_highlight_rules.ts b/src/plugins/console/public/application/models/legacy_core_editor/mode/output_highlight_rules.ts index 925bcde746b85..c9c76c562eeff 100644 --- a/src/plugins/console/public/application/models/legacy_core_editor/mode/output_highlight_rules.ts +++ b/src/plugins/console/public/application/models/legacy_core_editor/mode/output_highlight_rules.ts @@ -12,8 +12,8 @@ import { addXJsonToRules } from '@kbn/ace'; const JsonHighlightRules = ace.acequire('ace/mode/json_highlight_rules').JsonHighlightRules; -export const mapStatusCodeToBadge = (value: string) => { - const regExpMatchArray = value.match(/\d+/); +export const mapStatusCodeToBadge = (value?: string) => { + const regExpMatchArray = value?.match(/\d+/); if (regExpMatchArray) { const status = parseInt(regExpMatchArray[0], 10); if (status <= 199) { @@ -44,11 +44,15 @@ export class OutputJsonHighlightRules extends JsonHighlightRules { }, { token: 'comment', - regex: /#(.*?)(?=\d+\s(?:[\sA-Za-z]+)|$)/, + // match a comment starting with a hash at the start of the line + // ignore status codes and status texts at the end of the line (e.g. # GET _search/foo 200, # GET _search/foo 200 OK) + regex: /#(.*?)(?=[1-5][0-9][0-9]\s(?:[\sA-Za-z]+)|(?:[1-5][0-9][0-9])|$)/, }, { token: mapStatusCodeToBadge, - regex: /(\d+\s[\sA-Za-z]+$)/, + // match status codes and status texts at the end of the line (e.g. # GET _search/foo 200, # GET _search/foo 200 OK) + // this rule allows us to highlight them with the corresponding badge color (e.g. 200 OK -> badge.badge--success) + regex: /([1-5][0-9][0-9]\s?[\sA-Za-z]+$)/, } ); diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx index cc683ba63149c..9927f4c43c1bf 100644 --- a/src/plugins/dashboard/public/application/dashboard_router.tsx +++ b/src/plugins/dashboard/public/application/dashboard_router.tsx @@ -10,11 +10,15 @@ import './index.scss'; import React from 'react'; import { History } from 'history'; import { Provider } from 'react-redux'; +import { I18nProvider, FormattedRelative } from '@kbn/i18n-react'; import { parse, ParsedQuery } from 'query-string'; import { render, unmountComponentAtNode } from 'react-dom'; import { Switch, Route, RouteComponentProps, HashRouter, Redirect } from 'react-router-dom'; - -import { I18nProvider } from '@kbn/i18n-react'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { + TableListViewKibanaDependencies, + TableListViewKibanaProvider, +} from '@kbn/content-management-table-list'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { createKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public'; import { AppMountParameters, CoreSetup } from '@kbn/core/public'; @@ -35,6 +39,7 @@ import { } from '../types'; import { DashboardStart, DashboardStartDependencies } from '../plugin'; import { pluginServices } from '../services/plugin_services'; +import { DashboardApplicationService } from '../services/application/types'; export const dashboardUrlParams = { showTopMenu: 'show-top-menu', @@ -50,15 +55,24 @@ export interface DashboardMountProps { mountContext: DashboardMountContextProps; } +// because the type of `application.capabilities.advancedSettings` is so generic, the provider +// requiring the `save` key to be part of it is causing type issues - so, creating a custom type +type TableListViewApplicationService = DashboardApplicationService & { + capabilities: { advancedSettings: { save: boolean } }; +}; + export async function mountApp({ core, element, appUnMounted, mountContext }: DashboardMountProps) { const [, , dashboardStart] = await core.getStartServices(); // TODO: Remove as part of https://github.com/elastic/kibana/pull/138774 const { DashboardMountContext } = await import('./hooks/dashboard_mount_context'); const { + application, chrome: { setBadge, docTitle }, dashboardCapabilities: { showWriteControls }, data: dataStart, embeddable, + notifications, + savedObjectsTagging, settings: { uiSettings }, } = pluginServices.getServices(); @@ -164,26 +178,42 @@ export async function mountApp({ core, element, appUnMounted, mountContext }: Da - - - - - - - - - - + + + + + + + + + + + + diff --git a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap index 064daca6db30b..6fac25e51ddf1 100644 --- a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap +++ b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap @@ -21,30 +21,7 @@ exports[`after fetch When given a title that matches multiple dashboards, filter redirectTo={[MockFunction]} title="search by title" > - + + /> + `; @@ -162,30 +114,7 @@ exports[`after fetch initialFilter 1`] = ` } redirectTo={[MockFunction]} > - + + /> + `; @@ -302,30 +206,7 @@ exports[`after fetch renders all table rows 1`] = ` } redirectTo={[MockFunction]} > - + + /> + `; @@ -442,30 +298,7 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = ` } redirectTo={[MockFunction]} > - + + /> + `; @@ -582,30 +390,7 @@ exports[`after fetch renders call to action with continue when no dashboards exi } redirectTo={[MockFunction]} > - + + /> + `; @@ -733,30 +492,7 @@ exports[`after fetch showWriteControls 1`] = ` } redirectTo={[MockFunction]} > - + + /> + `; diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx index d690c91574eef..dae5308cc0234 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx @@ -9,10 +9,14 @@ import React from 'react'; import { mount } from 'enzyme'; -import { I18nProvider } from '@kbn/i18n-react'; +import { I18nProvider, FormattedRelative } from '@kbn/i18n-react'; import { SimpleSavedObject } from '@kbn/core/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; +import { + TableListViewKibanaDependencies, + TableListViewKibanaProvider, +} from '@kbn/content-management-table-list'; import { DashboardAppServices } from '../../types'; import { DashboardListing, DashboardListingProps } from './dashboard_listing'; @@ -39,13 +43,28 @@ function mountWith({ const wrappingComponent: React.FC<{ children: React.ReactNode; }> = ({ children }) => { - const DashboardServicesProvider = pluginServices.getContextProvider(); + const { application, notifications, savedObjectsTagging } = pluginServices.getServices(); return ( {/* Can't get rid of KibanaContextProvider here yet because of 'call to action when no dashboards exist' tests below */} - {children} + () => () => undefined} + > + {children} + ); diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx index 0d72523e913e2..40753c556a56a 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx @@ -11,18 +11,18 @@ import { EuiLink, EuiButton, EuiEmptyPrompt, - EuiBasicTableColumn, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, } from '@elastic/eui'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import type { ApplicationStart, SavedObjectsFindOptionsReference } from '@kbn/core/public'; +import type { SavedObjectsFindOptionsReference } from '@kbn/core/public'; import useMount from 'react-use/lib/useMount'; -import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; +import type { SavedObjectReference } from '@kbn/core/types'; +import { useExecutionContext, useKibana } from '@kbn/kibana-react-plugin/public'; import { syncGlobalQueryStateWithUrl } from '@kbn/data-plugin/public'; -import { TableListView, useKibana } from '@kbn/kibana-react-plugin/public'; import type { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; +import { TableListView, type UserContentCommonSchema } from '@kbn/content-management-table-list'; import { attemptLoadDashboardByTitle } from '../lib'; import { DashboardAppServices, DashboardRedirect } from '../../types'; @@ -43,6 +43,30 @@ import { DASHBOARD_PANELS_UNSAVED_ID } from '../../services/dashboard_session_st const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit'; const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage'; +interface DashboardSavedObjectUserContent extends UserContentCommonSchema { + attributes: { + title: string; + description?: string; + timeRestore: boolean; + }; +} + +const toTableListViewSavedObject = ( + savedObject: Record +): DashboardSavedObjectUserContent => { + return { + id: savedObject.id as string, + updatedAt: savedObject.updatedAt! as string, + references: savedObject.references as SavedObjectReference[], + type: 'dashboard', + attributes: { + title: (savedObject.title as string) ?? '', + description: savedObject.description as string, + timeRestore: savedObject.timeRestore as boolean, + }, + }; +}; + export interface DashboardListingProps { kbnUrlStateStorage: IKbnUrlStateStorage; redirectTo: DashboardRedirect; @@ -67,10 +91,8 @@ export const DashboardListing = ({ dashboardCapabilities: { showWriteControls }, dashboardSessionStorage, data: { query }, - notifications: { toasts }, savedObjects: { client }, - savedObjectsTagging: { getSearchBarFilter, parseSearchQuery }, - settings: { uiSettings, theme }, + settings: { uiSettings }, } = pluginServices.getServices(); const [showNoDataPage, setShowNoDataPage] = useState(false); @@ -122,11 +144,6 @@ export const DashboardListing = ({ const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING); const defaultFilter = title ? `"${title}"` : ''; - const tableColumns = useMemo( - () => getTableColumns(kbnUrlStateStorage, uiSettings.get('state:storeInSessionStorage')), - [uiSettings, kbnUrlStateStorage] - ); - const createItem = useCallback(() => { if (!dashboardSessionStorage.dashboardHasUnsavedEdits()) { redirectTo({ destination: 'dashboard' }); @@ -244,23 +261,20 @@ export const DashboardListing = ({ ]); const fetchItems = useCallback( - (filter: string) => { - let searchTerm = filter; - let references: SavedObjectsFindOptionsReference[] | undefined; - if (parseSearchQuery) { - const parsed = parseSearchQuery(filter, { - useName: true, + (searchTerm: string, references?: SavedObjectsFindOptionsReference[]) => { + return savedDashboards + .find(searchTerm, { + hasReference: references, + size: listingLimit, + }) + .then(({ total, hits }) => { + return { + total, + hits: hits.map(toTableListViewSavedObject), + }; }); - searchTerm = parsed.searchTerm; - references = parsed.tagReferences; - } - - return savedDashboards.find(searchTerm, { - size: listingLimit, - hasReference: references, - }); }, - [listingLimit, savedDashboards, parseSearchQuery] + [listingLimit, savedDashboards] ); const deleteItems = useCallback( @@ -278,42 +292,32 @@ export const DashboardListing = ({ [redirectTo] ); - const searchFilters = useMemo(() => { - const searchBarFilter = getSearchBarFilter?.({ useName: true }); - return searchBarFilter ? [searchBarFilter] : []; - }, [getSearchBarFilter]); - - const { getEntityName, getTableCaption, getTableListTitle, getEntityNamePlural } = - dashboardListingTable; + const { getEntityName, getTableListTitle, getEntityNamePlural } = dashboardListingTable; return ( <> {showNoDataPage && ( setShowNoDataPage(false)} /> )} {!showNoDataPage && ( - createItem={!showWriteControls ? undefined : createItem} deleteItems={!showWriteControls ? undefined : deleteItems} initialPageSize={initialPageSize} editItem={!showWriteControls ? undefined : editItem} initialFilter={initialFilter ?? defaultFilter} - toastNotifications={toasts} headingId="dashboardListingHeading" findItems={fetchItems} - rowHeader="title" entityNamePlural={getEntityNamePlural()} tableListTitle={getTableListTitle()} - tableCaption={getTableCaption()} entityName={getEntityName()} {...{ emptyPrompt, - searchFilters, listingLimit, - tableColumns, }} - theme={theme} - // The below type conversion is necessary until the TableListView component allows partial services - application={application as unknown as ApplicationStart} + id="dashboard" + getDetailViewLink={({ id, attributes: { timeRestore } }) => + getDashboardListItemLink(kbnUrlStateStorage, id, timeRestore) + } > ); }; - -const getTableColumns = (kbnUrlStateStorage: IKbnUrlStateStorage, useHash: boolean) => { - const { - savedObjectsTagging: { getTableColumnDefinition }, - } = pluginServices.getServices(); - const tableColumnDefinition = getTableColumnDefinition?.(); - - return [ - { - field: 'title', - name: dashboardListingTable.getTitleColumnName(), - sortable: true, - render: (field: string, record: { id: string; title: string; timeRestore: boolean }) => ( - - {field} - - ), - }, - { - field: 'description', - name: dashboardListingTable.getDescriptionColumnName(), - render: (field: string, record: { description: string }) => {record.description}, - sortable: true, - }, - ...(tableColumnDefinition ? [tableColumnDefinition] : []), - ] as unknown as Array>>; -}; diff --git a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts index 31ebeb9e93c9b..f44b4036dfcfd 100644 --- a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts +++ b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts @@ -23,12 +23,12 @@ kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, { time: { from: 'now-7d', to: ' describe('listing dashboard link', () => { test('creates a link to a dashboard without the timerange query if time is saved on the dashboard', async () => { - const url = getDashboardListItemLink(kbnUrlStateStorage, false, DASHBOARD_ID, true); + const url = getDashboardListItemLink(kbnUrlStateStorage, DASHBOARD_ID, true); expect(url).toMatchInlineSnapshot(`"http://localhost/#/?_g=()"`); }); test('creates a link to a dashboard with the timerange query if time is not saved on the dashboard', async () => { - const url = getDashboardListItemLink(kbnUrlStateStorage, false, DASHBOARD_ID, false); + const url = getDashboardListItemLink(kbnUrlStateStorage, DASHBOARD_ID, false); expect(url).toMatchInlineSnapshot(`"http://localhost/#/?_g=(time:(from:now-7d,to:now))"`); }); }); @@ -44,7 +44,7 @@ describe('when global time changes', () => { }); test('propagates the correct time on the query', async () => { - const url = getDashboardListItemLink(kbnUrlStateStorage, false, DASHBOARD_ID, false); + const url = getDashboardListItemLink(kbnUrlStateStorage, DASHBOARD_ID, false); expect(url).toMatchInlineSnapshot( `"http://localhost/#/?_g=(time:(from:'2021-01-05T11:45:53.375Z',to:'2021-01-21T11:46:00.990Z'))"` ); @@ -59,7 +59,7 @@ describe('when global refreshInterval changes', () => { }); test('propagates the refreshInterval on the query', async () => { - const url = getDashboardListItemLink(kbnUrlStateStorage, false, DASHBOARD_ID, false); + const url = getDashboardListItemLink(kbnUrlStateStorage, DASHBOARD_ID, false); expect(url).toMatchInlineSnapshot( `"http://localhost/#/?_g=(refreshInterval:(pause:!f,value:300))"` ); @@ -95,7 +95,7 @@ describe('when global filters change', () => { }); test('propagates the filters on the query', async () => { - const url = getDashboardListItemLink(kbnUrlStateStorage, false, DASHBOARD_ID, false); + const url = getDashboardListItemLink(kbnUrlStateStorage, DASHBOARD_ID, false); expect(url).toMatchInlineSnapshot( `"http://localhost/#/?_g=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1)),('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))))"` ); diff --git a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts index 39106f12551bc..0ee6f016ad6d5 100644 --- a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts +++ b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts @@ -18,13 +18,14 @@ import { pluginServices } from '../../services/plugin_services'; export const getDashboardListItemLink = ( kbnUrlStateStorage: IKbnUrlStateStorage, - useHash: boolean, id: string, timeRestore: boolean ) => { const { application: { getUrlForApp }, + settings: { uiSettings }, } = pluginServices.getServices(); + const useHash = uiSettings.get('state:storeInSessionStorage'); // use hash let url = getUrlForApp(DashboardConstants.DASHBOARDS_ID, { path: `#${createDashboardEditUrl(id)}`, diff --git a/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts b/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts index 2712c92888bf3..3ffe9dc3b70e9 100644 --- a/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts +++ b/src/plugins/dashboard/public/application/test_helpers/make_default_services.ts @@ -24,6 +24,13 @@ export function makeDefaultServices(): DashboardAppServices { id: `dashboard${i}`, title: `dashboard${i} - ${search} - title`, description: `dashboard${i} desc`, + references: [], + timeRestore: true, + type: '', + url: '', + updatedAt: '', + panelsJSON: '', + lastSavedTitle: '', }); } return Promise.resolve({ diff --git a/src/plugins/dashboard/public/dashboard_strings.ts b/src/plugins/dashboard/public/dashboard_strings.ts index 7cea0a45c0e25..5679ac28f838b 100644 --- a/src/plugins/dashboard/public/dashboard_strings.ts +++ b/src/plugins/dashboard/public/dashboard_strings.ts @@ -442,15 +442,6 @@ export const dashboardListingTable = { defaultMessage: 'dashboards', }), getTableListTitle: () => getDashboardPageTitle(), - getTableCaption: () => getDashboardPageTitle(), - getTitleColumnName: () => - i18n.translate('dashboard.listing.table.titleColumnName', { - defaultMessage: 'Title', - }), - getDescriptionColumnName: () => - i18n.translate('dashboard.listing.table.descriptionColumnName', { - defaultMessage: 'Description', - }), }; export const dashboardUnsavedListingStrings = { diff --git a/src/plugins/discover/public/__mocks__/data_view.ts b/src/plugins/discover/public/__mocks__/data_view.ts index c4935d2f41f91..8a69435949d4d 100644 --- a/src/plugins/discover/public/__mocks__/data_view.ts +++ b/src/plugins/discover/public/__mocks__/data_view.ts @@ -46,6 +46,7 @@ const fields = [ scripted: false, filterable: true, aggregatable: true, + sortable: true, }, { name: 'scripted', diff --git a/src/plugins/discover/public/__mocks__/saved_search.ts b/src/plugins/discover/public/__mocks__/saved_search.ts index 1f430de118317..de97a31d65632 100644 --- a/src/plugins/discover/public/__mocks__/saved_search.ts +++ b/src/plugins/discover/public/__mocks__/saved_search.ts @@ -20,3 +20,11 @@ export const savedSearchMockWithTimeField = { id: 'the-saved-search-id-with-timefield', searchSource: createSearchSourceMock({ index: dataViewWithTimefieldMock }), } as unknown as SavedSearch; + +export const savedSearchMockWithSQL = { + id: 'the-saved-search-id-sql', + searchSource: createSearchSourceMock({ + index: dataViewWithTimefieldMock, + query: { sql: 'SELECT * FROM "the-saved-search-id-sql"' }, + }), +} as unknown as SavedSearch; diff --git a/src/plugins/discover/public/__mocks__/services.ts b/src/plugins/discover/public/__mocks__/services.ts index ebec857a80b2d..953422acc9291 100644 --- a/src/plugins/discover/public/__mocks__/services.ts +++ b/src/plugins/discover/public/__mocks__/services.ts @@ -63,7 +63,7 @@ export const discoverServiceMock = { if (key === 'fields:popularLimit') { return 5; } else if (key === DEFAULT_COLUMNS_SETTING) { - return []; + return ['default_column']; } else if (key === UI_SETTINGS.META_FIELDS) { return []; } else if (key === DOC_HIDE_TIME_COLUMN_SETTING) { diff --git a/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx b/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx index da2f0810706b8..4bc60e904bb84 100644 --- a/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx +++ b/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; +import React, { memo, ReactElement, useCallback, useEffect, useRef, useState } from 'react'; import moment from 'moment'; import { EuiButtonIcon, @@ -14,7 +14,6 @@ import { EuiFlexItem, EuiPopover, EuiToolTip, - EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { DataView } from '@kbn/data-views-plugin/public'; @@ -24,8 +23,6 @@ import { GetStateReturn } from '../../services/discover_state'; import { DiscoverHistogram } from './histogram'; import { DataCharts$, DataTotalHits$ } from '../../hooks/use_saved_search'; import { useChartPanels } from './use_chart_panels'; -import { VIEW_MODE, DocumentViewModeToggle } from '../../../../components/view_mode_toggle'; -import { SHOW_FIELD_STATISTICS } from '../../../../../common'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { getVisualizeInformation, @@ -36,33 +33,32 @@ const DiscoverHistogramMemoized = memo(DiscoverHistogram); export const CHART_HIDDEN_KEY = 'discover:chartHidden'; export function DiscoverChart({ + className, resetSavedSearch, savedSearch, savedSearchDataChart$, savedSearchDataTotalHits$, stateContainer, dataView, - viewMode, - setDiscoverViewMode, hideChart, interval, isTimeBased, + appendHistogram, }: { + className?: string; resetSavedSearch: () => void; savedSearch: SavedSearch; savedSearchDataChart$: DataCharts$; savedSearchDataTotalHits$: DataTotalHits$; stateContainer: GetStateReturn; dataView: DataView; - viewMode: VIEW_MODE; - setDiscoverViewMode: (viewMode: VIEW_MODE) => void; isTimeBased: boolean; hideChart?: boolean; interval?: string; + appendHistogram?: ReactElement; }) { - const { uiSettings, data, storage } = useDiscoverServices(); + const { data, storage } = useDiscoverServices(); const [showChartOptionsPopover, setShowChartOptionsPopover] = useState(false); - const showViewModeToggle = uiSettings.get(SHOW_FIELD_STATISTICS) ?? false; const chartRef = useRef<{ element: HTMLElement | null; moveFocus: boolean }>({ element: null, @@ -126,9 +122,15 @@ export function DiscoverChart({ ); return ( - + - + - {showViewModeToggle && ( - - - - )} {isTimeBased && ( @@ -203,7 +197,7 @@ export function DiscoverChart({ {isTimeBased && !hideChart && ( - +
(chartRef.current.element = element)} tabIndex={-1} @@ -218,7 +212,7 @@ export function DiscoverChart({ stateContainer={stateContainer} />
- + {appendHistogram}
)}
diff --git a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx index 59d8fae4afbeb..cd3849d3cf4d3 100644 --- a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx +++ b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx @@ -18,6 +18,8 @@ import { isErrorEmbeddable, } from '@kbn/embeddable-plugin/public'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { EuiFlexItem } from '@elastic/eui'; +import { css } from '@emotion/react'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { FIELD_STATISTICS_LOADED } from './constants'; import type { GetStateReturn } from '../../services/discover_state'; @@ -226,13 +228,22 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { }; }, [embeddable, embeddableRoot, trackUiMetric]); + const statsTableCss = css` + overflow-y: auto; + + .kbnDocTableWrapper { + overflow-x: hidden; + } + `; + return ( -
+ +
+ ); }; diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.scss b/src/plugins/discover/public/application/main/components/layout/discover_layout.scss index 9ea41f343b885..ee727779fc2a1 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.scss +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.scss @@ -70,7 +70,9 @@ discover-app { } .dscTimechart { - display: block; + flex-grow: 1; + display: flex; + flex-direction: column; position: relative; // SASSTODO: the visualizing component should have an option or a modifier @@ -81,13 +83,12 @@ discover-app { } .dscHistogram { - height: $euiSize * 7; - padding: 0 $euiSizeS $euiSizeS * 2 $euiSizeS; + flex-grow: 1; + padding: 0 $euiSizeS $euiSizeS $euiSizeS; } .dscHistogramTimeRange { padding: 0 $euiSizeS 0 $euiSizeS; - margin-top: - $euiSizeS; } .dscTable { diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx index 678e44b3ca444..39db1481500fa 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx @@ -175,24 +175,27 @@ function mountComponent( describe('Discover component', () => { test('selected data view without time field displays no chart toggle', () => { - const component = mountComponent(dataViewMock); - expect(component.find('[data-test-subj="discoverChartOptionsToggle"]').exists()).toBeFalsy(); + const container = document.createElement('div'); + mountComponent(dataViewMock, undefined, { attachTo: container }); + expect(container.querySelector('[data-test-subj="discoverChartOptionsToggle"]')).toBeNull(); }); test('selected data view with time field displays chart toggle', () => { - const component = mountComponent(dataViewWithTimefieldMock); - expect(component.find('[data-test-subj="discoverChartOptionsToggle"]').exists()).toBeTruthy(); + const container = document.createElement('div'); + mountComponent(dataViewWithTimefieldMock, undefined, { attachTo: container }); + expect(container.querySelector('[data-test-subj="discoverChartOptionsToggle"]')).not.toBeNull(); }); test('sql query displays no chart toggle', () => { - const component = mountComponent( + const container = document.createElement('div'); + mountComponent( dataViewWithTimefieldMock, false, - {}, + { attachTo: container }, { sql: 'SELECT * FROM test' }, true ); - expect(component.find('[data-test-subj="discoverChartOptionsToggle"]').exists()).toBeFalsy(); + expect(container.querySelector('[data-test-subj="discoverChartOptionsToggle"]')).toBeNull(); }); test('the saved search title h1 gains focus on navigate', () => { 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 76eccfe2813ec..fbd58853d797c 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 @@ -12,7 +12,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiHideFor, - EuiHorizontalRule, EuiPage, EuiPageBody, EuiPageContent_Deprecated as EuiPageContent, @@ -34,20 +33,17 @@ import { SEARCH_FIELDS_FROM_SOURCE, SHOW_FIELD_STATISTICS } from '../../../../.. import { popularizeField } from '../../../../utils/popularize_field'; import { DiscoverTopNav } from '../top_nav/discover_topnav'; import { DocViewFilterFn } from '../../../../services/doc_views/doc_views_types'; -import { DiscoverChart } from '../chart'; import { getResultState } from '../../utils/get_result_state'; import { DiscoverUninitialized } from '../uninitialized/uninitialized'; import { DataMainMsg, RecordRawType } from '../../hooks/use_saved_search'; import { useColumns } from '../../../../hooks/use_data_grid_columns'; -import { DiscoverDocuments } from './discover_documents'; import { FetchStatus } from '../../../types'; import { useDataState } from '../../hooks/use_data_state'; -import { FieldStatisticsTable } from '../field_stats_table'; import { VIEW_MODE } from '../../../../components/view_mode_toggle'; -import { DOCUMENTS_VIEW_CLICK, FIELD_STATISTICS_VIEW_CLICK } from '../field_stats_table/constants'; 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 { DiscoverMainContent } from './discover_main_content'; /** * Local storage key for sidebar persistence state @@ -56,8 +52,6 @@ export const SIDEBAR_CLOSED_KEY = 'discover:sidebarClosed'; const SidebarMemoized = React.memo(DiscoverSidebarResponsive); const TopNavMemoized = React.memo(DiscoverTopNav); -const DiscoverChartMemoized = React.memo(DiscoverChart); -const FieldStatisticsTableMemoized = React.memo(FieldStatisticsTable); export function DiscoverLayout({ dataView, @@ -91,7 +85,7 @@ export function DiscoverLayout({ spaces, inspector, } = useDiscoverServices(); - const { main$, charts$, totalHits$ } = savedSearchData$; + const { main$ } = savedSearchData$; const dataState: DataMainMsg = useDataState(main$); const viewMode = useMemo(() => { @@ -99,21 +93,6 @@ export function DiscoverLayout({ return state.viewMode ?? VIEW_MODE.DOCUMENT_LEVEL; }, [uiSettings, state.viewMode]); - const setDiscoverViewMode = useCallback( - (mode: VIEW_MODE) => { - stateContainer.setAppState({ viewMode: mode }); - - if (trackUiMetric) { - if (mode === VIEW_MODE.AGGREGATED_LEVEL) { - trackUiMetric(METRIC_TYPE.CLICK, FIELD_STATISTICS_VIEW_CLICK); - } else { - trackUiMetric(METRIC_TYPE.CLICK, DOCUMENTS_VIEW_CLICK); - } - } - }, - [trackUiMetric, stateContainer] - ); - const fetchCounter = useRef(0); useEffect(() => { @@ -210,6 +189,8 @@ export function DiscoverLayout({ } }, [dataState.error, isPlainRecord]); + const resizeRef = useRef(null); + return (

} {resultState === 'ready' && ( - - {!isPlainRecord && ( - <> - - - - - - )} - {viewMode === VIEW_MODE.DOCUMENT_LEVEL ? ( - - ) : ( - - )} - + )} diff --git a/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx new file mode 100644 index 0000000000000..6b499aa62c430 --- /dev/null +++ b/src/plugins/discover/public/application/main/components/layout/discover_main_content.test.tsx @@ -0,0 +1,254 @@ +/* + * Copyright 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 { Subject, BehaviorSubject } from 'rxjs'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { esHits } from '../../../../__mocks__/es_hits'; +import { dataViewMock } from '../../../../__mocks__/data_view'; +import { savedSearchMock } from '../../../../__mocks__/saved_search'; +import { GetStateReturn } from '../../services/discover_state'; +import { + AvailableFields$, + DataCharts$, + DataDocuments$, + DataMain$, + DataTotalHits$, + RecordRawType, +} from '../../hooks/use_saved_search'; +import { discoverServiceMock } from '../../../../__mocks__/services'; +import { FetchStatus } from '../../../types'; +import { Chart } from '../chart/point_series'; +import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { buildDataTableRecord } from '../../../../utils/build_data_record'; +import { DiscoverMainContent, DiscoverMainContentProps } from './discover_main_content'; +import { VIEW_MODE } from '@kbn/saved-search-plugin/public'; +import { DiscoverPanels, DISCOVER_PANELS_MODE } from './discover_panels'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { CoreTheme } from '@kbn/core/public'; +import { act } from 'react-dom/test-utils'; +import { setTimeout } from 'timers/promises'; +import { DiscoverChart } from '../chart'; +import { ReactWrapper } from 'enzyme'; +import { DocumentViewModeToggle } from '../../../../components/view_mode_toggle'; + +const mountComponent = async ({ + isPlainRecord = false, + hideChart = false, + isTimeBased = true, +}: { + isPlainRecord?: boolean; + hideChart?: boolean; + isTimeBased?: boolean; +} = {}) => { + const services = discoverServiceMock; + services.data.query.timefilter.timefilter.getAbsoluteTime = () => { + return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' }; + }; + + const main$ = new BehaviorSubject({ + fetchStatus: FetchStatus.COMPLETE, + recordRawType: isPlainRecord ? RecordRawType.PLAIN : RecordRawType.DOCUMENT, + foundDocuments: true, + }) as DataMain$; + + const documents$ = new BehaviorSubject({ + fetchStatus: FetchStatus.COMPLETE, + result: esHits.map((esHit) => buildDataTableRecord(esHit, dataViewMock)), + }) as DataDocuments$; + + const availableFields$ = new BehaviorSubject({ + fetchStatus: FetchStatus.COMPLETE, + fields: [] as string[], + }) as AvailableFields$; + + const totalHits$ = new BehaviorSubject({ + fetchStatus: FetchStatus.COMPLETE, + result: Number(esHits.length), + }) as DataTotalHits$; + + const chartData = { + xAxisOrderedValues: [ + 1623880800000, 1623967200000, 1624053600000, 1624140000000, 1624226400000, 1624312800000, + 1624399200000, 1624485600000, 1624572000000, 1624658400000, 1624744800000, 1624831200000, + 1624917600000, 1625004000000, 1625090400000, + ], + xAxisFormat: { id: 'date', params: { pattern: 'YYYY-MM-DD' } }, + xAxisLabel: 'order_date per day', + yAxisFormat: { id: 'number' }, + ordered: { + date: true, + interval: { + asMilliseconds: jest.fn(), + }, + intervalESUnit: 'd', + intervalESValue: 1, + min: '2021-03-18T08:28:56.411Z', + max: '2021-07-01T07:28:56.411Z', + }, + yAxisLabel: 'Count', + values: [ + { x: 1623880800000, y: 134 }, + { x: 1623967200000, y: 152 }, + { x: 1624053600000, y: 141 }, + { x: 1624140000000, y: 138 }, + { x: 1624226400000, y: 142 }, + { x: 1624312800000, y: 157 }, + { x: 1624399200000, y: 149 }, + { x: 1624485600000, y: 146 }, + { x: 1624572000000, y: 170 }, + { x: 1624658400000, y: 137 }, + { x: 1624744800000, y: 150 }, + { x: 1624831200000, y: 144 }, + { x: 1624917600000, y: 147 }, + { x: 1625004000000, y: 137 }, + { x: 1625090400000, y: 66 }, + ], + } as unknown as Chart; + + const charts$ = new BehaviorSubject({ + fetchStatus: FetchStatus.COMPLETE, + chartData, + bucketInterval: { + scaled: true, + description: 'test', + scale: 2, + }, + }) as DataCharts$; + + const savedSearchData$ = { + main$, + documents$, + totalHits$, + charts$, + availableFields$, + }; + + const props: DiscoverMainContentProps = { + isPlainRecord, + dataView: dataViewMock, + navigateTo: jest.fn(), + resetSavedSearch: jest.fn(), + setExpandedDoc: jest.fn(), + savedSearch: savedSearchMock, + savedSearchData$, + savedSearchRefetch$: new Subject(), + state: { columns: [], hideChart }, + stateContainer: { + setAppState: () => {}, + appStateContainer: { + getState: () => ({ + interval: 'auto', + }), + }, + } as unknown as GetStateReturn, + isTimeBased, + viewMode: VIEW_MODE.DOCUMENT_LEVEL, + onAddFilter: jest.fn(), + onFieldEdited: jest.fn(), + columns: [], + resizeRef: { current: null }, + }; + + const coreTheme$ = new BehaviorSubject({ darkMode: false }); + + const component = mountWithIntl( + + + + + + ); + + // useIsWithinBreakpoints triggers state updates which cause act + // issues and prevent our resize events from being fired correctly + // https://github.com/enzymejs/enzyme/issues/2073 + await act(() => setTimeout(0)); + + return component; +}; + +const setWindowWidth = (component: ReactWrapper, width: string) => { + window.innerWidth = parseInt(width, 10); + act(() => { + window.dispatchEvent(new Event('resize')); + }); + component.update(); +}; + +describe('Discover main content component', () => { + const windowWidth = window.innerWidth; + + beforeEach(() => { + window.innerWidth = windowWidth; + }); + + it('should set the panels mode to DISCOVER_PANELS_MODE.RESIZABLE when viewing on medium screens and above', async () => { + const component = await mountComponent(); + setWindowWidth(component, euiThemeVars.euiBreakpoints.m); + expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.RESIZABLE); + }); + + it('should set the panels mode to DISCOVER_PANELS_MODE.FIXED when viewing on small screens and below', async () => { + const component = await mountComponent(); + setWindowWidth(component, euiThemeVars.euiBreakpoints.s); + expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.FIXED); + }); + + it('should set the panels mode to DISCOVER_PANELS_MODE.FIXED if hideChart is true', async () => { + const component = await mountComponent({ hideChart: true }); + expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.FIXED); + }); + + it('should set the panels mode to DISCOVER_PANELS_MODE.FIXED if isTimeBased is false', async () => { + const component = await mountComponent({ isTimeBased: false }); + expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.FIXED); + }); + + it('should set the panels mode to DISCOVER_PANELS_MODE.SINGLE if isPlainRecord is true', async () => { + const component = await mountComponent({ isPlainRecord: true }); + expect(component.find(DiscoverPanels).prop('mode')).toBe(DISCOVER_PANELS_MODE.SINGLE); + }); + + it('should set a fixed height for DiscoverChart when panels mode is DISCOVER_PANELS_MODE.FIXED and hideChart is false', async () => { + const component = await mountComponent(); + setWindowWidth(component, euiThemeVars.euiBreakpoints.s); + const expectedHeight = component.find(DiscoverPanels).prop('initialTopPanelHeight'); + expect(component.find(DiscoverChart).childAt(0).getDOMNode()).toHaveStyle({ + height: `${expectedHeight}px`, + }); + }); + + it('should not set a fixed height for DiscoverChart when panels mode is DISCOVER_PANELS_MODE.FIXED and hideChart is true', async () => { + const component = await mountComponent({ hideChart: true }); + setWindowWidth(component, euiThemeVars.euiBreakpoints.s); + const expectedHeight = component.find(DiscoverPanels).prop('initialTopPanelHeight'); + expect(component.find(DiscoverChart).childAt(0).getDOMNode()).not.toHaveStyle({ + height: `${expectedHeight}px`, + }); + }); + + it('should not set a fixed height for DiscoverChart when panels mode is DISCOVER_PANELS_MODE.FIXED and isTimeBased is false', async () => { + const component = await mountComponent({ isTimeBased: false }); + setWindowWidth(component, euiThemeVars.euiBreakpoints.s); + const expectedHeight = component.find(DiscoverPanels).prop('initialTopPanelHeight'); + expect(component.find(DiscoverChart).childAt(0).getDOMNode()).not.toHaveStyle({ + height: `${expectedHeight}px`, + }); + }); + + it('should show DocumentViewModeToggle when isPlainRecord is false', async () => { + const component = await mountComponent(); + expect(component.find(DocumentViewModeToggle).exists()).toBe(true); + }); + + it('should not show DocumentViewModeToggle when isPlainRecord is true', async () => { + const component = await mountComponent({ isPlainRecord: true }); + expect(component.find(DocumentViewModeToggle).exists()).toBe(false); + }); +}); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx b/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx new file mode 100644 index 0000000000000..234b4392453e6 --- /dev/null +++ b/src/plugins/discover/public/application/main/components/layout/discover_main_content.tsx @@ -0,0 +1,199 @@ +/* + * Copyright 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 { + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiSpacer, + useEuiTheme, + useIsWithinBreakpoints, +} from '@elastic/eui'; +import { SavedSearch } from '@kbn/saved-search-plugin/public'; +import React, { RefObject, useCallback, useMemo } from 'react'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { METRIC_TYPE } from '@kbn/analytics'; +import { createHtmlPortalNode, InPortal, OutPortal } from 'react-reverse-portal'; +import { css } from '@emotion/css'; +import { useDiscoverServices } from '../../../../hooks/use_discover_services'; +import { DataTableRecord } from '../../../../types'; +import { DocumentViewModeToggle, VIEW_MODE } from '../../../../components/view_mode_toggle'; +import { DocViewFilterFn } from '../../../../services/doc_views/doc_views_types'; +import { DataRefetch$, SavedSearchData } from '../../hooks/use_saved_search'; +import { AppState, GetStateReturn } from '../../services/discover_state'; +import { DiscoverChart } from '../chart'; +import { FieldStatisticsTable } from '../field_stats_table'; +import { DiscoverDocuments } from './discover_documents'; +import { DOCUMENTS_VIEW_CLICK, FIELD_STATISTICS_VIEW_CLICK } from '../field_stats_table/constants'; +import { DiscoverPanels, DISCOVER_PANELS_MODE } from './discover_panels'; + +const DiscoverChartMemoized = React.memo(DiscoverChart); +const FieldStatisticsTableMemoized = React.memo(FieldStatisticsTable); + +export interface DiscoverMainContentProps { + isPlainRecord: boolean; + dataView: DataView; + navigateTo: (url: string) => void; + resetSavedSearch: () => void; + expandedDoc?: DataTableRecord; + setExpandedDoc: (doc?: DataTableRecord) => void; + savedSearch: SavedSearch; + savedSearchData$: SavedSearchData; + savedSearchRefetch$: DataRefetch$; + state: AppState; + stateContainer: GetStateReturn; + isTimeBased: boolean; + viewMode: VIEW_MODE; + onAddFilter: DocViewFilterFn | undefined; + onFieldEdited: () => void; + columns: string[]; + resizeRef: RefObject; +} + +export const DiscoverMainContent = ({ + isPlainRecord, + dataView, + navigateTo, + resetSavedSearch, + expandedDoc, + setExpandedDoc, + savedSearch, + savedSearchData$, + savedSearchRefetch$, + state, + stateContainer, + isTimeBased, + viewMode, + onAddFilter, + onFieldEdited, + columns, + resizeRef, +}: DiscoverMainContentProps) => { + const { trackUiMetric } = useDiscoverServices(); + + const setDiscoverViewMode = useCallback( + (mode: VIEW_MODE) => { + stateContainer.setAppState({ viewMode: mode }); + + if (trackUiMetric) { + if (mode === VIEW_MODE.AGGREGATED_LEVEL) { + trackUiMetric(METRIC_TYPE.CLICK, FIELD_STATISTICS_VIEW_CLICK); + } else { + trackUiMetric(METRIC_TYPE.CLICK, DOCUMENTS_VIEW_CLICK); + } + } + }, + [trackUiMetric, stateContainer] + ); + + const topPanelNode = useMemo( + () => createHtmlPortalNode({ attributes: { class: 'eui-fullHeight' } }), + [] + ); + + const mainPanelNode = useMemo( + () => createHtmlPortalNode({ attributes: { class: 'eui-fullHeight' } }), + [] + ); + + const hideChart = state.hideChart || !isTimeBased; + const showFixedPanels = useIsWithinBreakpoints(['xs', 's']) || isPlainRecord || hideChart; + const { euiTheme } = useEuiTheme(); + const topPanelHeight = euiTheme.base * 12; + const minTopPanelHeight = euiTheme.base * 8; + const minMainPanelHeight = euiTheme.base * 10; + + const chartClassName = + showFixedPanels && !hideChart + ? css` + height: ${topPanelHeight}px; + ` + : 'eui-fullHeight'; + + const panelsMode = isPlainRecord + ? DISCOVER_PANELS_MODE.SINGLE + : showFixedPanels + ? DISCOVER_PANELS_MODE.FIXED + : DISCOVER_PANELS_MODE.RESIZABLE; + + return ( + <> + + : } + /> + + + + {!isPlainRecord && ( + + {!showFixedPanels && } + + + + )} + {viewMode === VIEW_MODE.DOCUMENT_LEVEL ? ( + + ) : ( + + )} + + + } + mainPanel={} + /> + + ); +}; diff --git a/src/plugins/discover/public/application/main/components/layout/discover_panels.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_panels.test.tsx new file mode 100644 index 0000000000000..60ba0038f1194 --- /dev/null +++ b/src/plugins/discover/public/application/main/components/layout/discover_panels.test.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 { mount } from 'enzyme'; +import React, { ReactElement, RefObject } from 'react'; +import { DiscoverPanels, DISCOVER_PANELS_MODE } from './discover_panels'; +import { DiscoverPanelsResizable } from './discover_panels_resizable'; +import { DiscoverPanelsFixed } from './discover_panels_fixed'; + +describe('Discover panels component', () => { + const mountComponent = ({ + mode = DISCOVER_PANELS_MODE.RESIZABLE, + resizeRef = { current: null }, + initialTopPanelHeight = 200, + minTopPanelHeight = 100, + minMainPanelHeight = 100, + topPanel = <>, + mainPanel = <>, + }: { + mode?: DISCOVER_PANELS_MODE; + resizeRef?: RefObject; + initialTopPanelHeight?: number; + minTopPanelHeight?: number; + minMainPanelHeight?: number; + mainPanel?: ReactElement; + topPanel?: ReactElement; + }) => { + return mount( + + ); + }; + + it('should show DiscoverPanelsFixed when mode is DISCOVER_PANELS_MODE.SINGLE', () => { + const topPanel =
; + const mainPanel =
; + const component = mountComponent({ mode: DISCOVER_PANELS_MODE.SINGLE, topPanel, mainPanel }); + expect(component.find(DiscoverPanelsFixed).exists()).toBe(true); + expect(component.find(DiscoverPanelsResizable).exists()).toBe(false); + expect(component.contains(topPanel)).toBe(false); + expect(component.contains(mainPanel)).toBe(true); + }); + + it('should show DiscoverPanelsFixed when mode is DISCOVER_PANELS_MODE.FIXED', () => { + const topPanel =
; + const mainPanel =
; + const component = mountComponent({ mode: DISCOVER_PANELS_MODE.FIXED, topPanel, mainPanel }); + expect(component.find(DiscoverPanelsFixed).exists()).toBe(true); + expect(component.find(DiscoverPanelsResizable).exists()).toBe(false); + expect(component.contains(topPanel)).toBe(true); + expect(component.contains(mainPanel)).toBe(true); + }); + + it('should show DiscoverPanelsResizable when mode is DISCOVER_PANELS_MODE.RESIZABLE', () => { + const topPanel =
; + const mainPanel =
; + const component = mountComponent({ mode: DISCOVER_PANELS_MODE.RESIZABLE, topPanel, mainPanel }); + expect(component.find(DiscoverPanelsFixed).exists()).toBe(false); + expect(component.find(DiscoverPanelsResizable).exists()).toBe(true); + expect(component.contains(topPanel)).toBe(true); + expect(component.contains(mainPanel)).toBe(true); + }); + + it('should pass true for hideTopPanel when mode is DISCOVER_PANELS_MODE.SINGLE', () => { + const topPanel =
; + const mainPanel =
; + const component = mountComponent({ mode: DISCOVER_PANELS_MODE.SINGLE, topPanel, mainPanel }); + expect(component.find(DiscoverPanelsFixed).prop('hideTopPanel')).toBe(true); + expect(component.contains(topPanel)).toBe(false); + expect(component.contains(mainPanel)).toBe(true); + }); + + it('should pass false for hideTopPanel when mode is DISCOVER_PANELS_MODE.FIXED', () => { + const topPanel =
; + const mainPanel =
; + const component = mountComponent({ mode: DISCOVER_PANELS_MODE.FIXED, topPanel, mainPanel }); + expect(component.find(DiscoverPanelsFixed).prop('hideTopPanel')).toBe(false); + expect(component.contains(topPanel)).toBe(true); + expect(component.contains(mainPanel)).toBe(true); + }); +}); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_panels.tsx b/src/plugins/discover/public/application/main/components/layout/discover_panels.tsx new file mode 100644 index 0000000000000..57ea08ee92c8b --- /dev/null +++ b/src/plugins/discover/public/application/main/components/layout/discover_panels.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 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, { ReactElement, RefObject } from 'react'; +import { DiscoverPanelsResizable } from './discover_panels_resizable'; +import { DiscoverPanelsFixed } from './discover_panels_fixed'; + +export enum DISCOVER_PANELS_MODE { + SINGLE = 'single', + FIXED = 'fixed', + RESIZABLE = 'resizable', +} + +export interface DiscoverPanelsProps { + className?: string; + mode: DISCOVER_PANELS_MODE; + resizeRef: RefObject; + initialTopPanelHeight: number; + minTopPanelHeight: number; + minMainPanelHeight: number; + topPanel: ReactElement; + mainPanel: ReactElement; +} + +const fixedModes = [DISCOVER_PANELS_MODE.SINGLE, DISCOVER_PANELS_MODE.FIXED]; + +export const DiscoverPanels = ({ + className, + mode, + resizeRef, + initialTopPanelHeight, + minTopPanelHeight, + minMainPanelHeight, + topPanel, + mainPanel, +}: DiscoverPanelsProps) => { + const panelsProps = { className, topPanel, mainPanel }; + + return fixedModes.includes(mode) ? ( + + ) : ( + + ); +}; diff --git a/src/plugins/discover/public/application/main/components/layout/discover_panels_fixed.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_panels_fixed.test.tsx new file mode 100644 index 0000000000000..a4632f83c918c --- /dev/null +++ b/src/plugins/discover/public/application/main/components/layout/discover_panels_fixed.test.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { mount } from 'enzyme'; +import React, { ReactElement } from 'react'; +import { DiscoverPanelsFixed } from './discover_panels_fixed'; + +describe('Discover panels fixed', () => { + const mountComponent = ({ + hideTopPanel = false, + topPanel = <>, + mainPanel = <>, + }: { + hideTopPanel?: boolean; + topPanel: ReactElement; + mainPanel: ReactElement; + }) => { + return mount( + + ); + }; + + it('should render both panels when hideTopPanel is false', () => { + const topPanel =
; + const mainPanel =
; + const component = mountComponent({ topPanel, mainPanel }); + expect(component.contains(topPanel)).toBe(true); + expect(component.contains(mainPanel)).toBe(true); + }); + + it('should render only main panel when hideTopPanel is true', () => { + const topPanel =
; + const mainPanel =
; + const component = mountComponent({ hideTopPanel: true, topPanel, mainPanel }); + expect(component.contains(topPanel)).toBe(false); + expect(component.contains(mainPanel)).toBe(true); + }); +}); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_panels_fixed.tsx b/src/plugins/discover/public/application/main/components/layout/discover_panels_fixed.tsx new file mode 100644 index 0000000000000..1db99e61fb8c5 --- /dev/null +++ b/src/plugins/discover/public/application/main/components/layout/discover_panels_fixed.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { css } from '@emotion/react'; +import React, { ReactElement } from 'react'; + +export const DiscoverPanelsFixed = ({ + className, + hideTopPanel, + topPanel, + mainPanel, +}: { + className?: string; + hideTopPanel?: boolean; + topPanel: ReactElement; + mainPanel: ReactElement; +}) => { + // By default a flex item has overflow: visible, min-height: auto, and min-width: auto. + // This can cause the item to overflow the flexbox parent when its content is too large. + // Setting the overflow to something other than visible (e.g. auto) resets the min-height + // and min-width to 0 and makes the item respect the flexbox parent's size. + // https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size + const mainPanelCss = css` + overflow: auto; + `; + + return ( + + {!hideTopPanel && {topPanel}} + {mainPanel} + + ); +}; diff --git a/src/plugins/discover/public/application/main/components/layout/discover_panels_resizable.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_panels_resizable.test.tsx new file mode 100644 index 0000000000000..16c50a94b4656 --- /dev/null +++ b/src/plugins/discover/public/application/main/components/layout/discover_panels_resizable.test.tsx @@ -0,0 +1,188 @@ +/* + * Copyright 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 { mount, ReactWrapper } from 'enzyme'; +import React, { ReactElement, RefObject } from 'react'; +import { DiscoverPanelsResizable } from './discover_panels_resizable'; +import { act } from 'react-dom/test-utils'; + +const containerHeight = 1000; +const topPanelId = 'topPanel'; + +jest.mock('@elastic/eui', () => ({ + ...jest.requireActual('@elastic/eui'), + useResizeObserver: jest.fn(), + useGeneratedHtmlId: jest.fn(() => topPanelId), +})); + +import * as eui from '@elastic/eui'; +import { waitFor } from '@testing-library/dom'; + +describe('Discover panels resizable', () => { + const mountComponent = ({ + className = '', + resizeRef = { current: null }, + initialTopPanelHeight = 0, + minTopPanelHeight = 0, + minMainPanelHeight = 0, + topPanel = <>, + mainPanel = <>, + attachTo, + }: { + className?: string; + resizeRef?: RefObject; + initialTopPanelHeight?: number; + minTopPanelHeight?: number; + minMainPanelHeight?: number; + topPanel?: ReactElement; + mainPanel?: ReactElement; + attachTo?: HTMLElement; + }) => { + return mount( + , + attachTo ? { attachTo } : undefined + ); + }; + + const expectCorrectPanelSizes = ( + component: ReactWrapper, + currentContainerHeight: number, + topPanelHeight: number + ) => { + const topPanelSize = (topPanelHeight / currentContainerHeight) * 100; + expect(component.find('[data-test-subj="dscResizablePanelTop"]').at(0).prop('size')).toBe( + topPanelSize + ); + expect(component.find('[data-test-subj="dscResizablePanelMain"]').at(0).prop('size')).toBe( + 100 - topPanelSize + ); + }; + + const forceRender = (component: ReactWrapper) => { + component.setProps({}).update(); + }; + + beforeEach(() => { + jest.spyOn(eui, 'useResizeObserver').mockReturnValue({ height: containerHeight, width: 0 }); + }); + + it('should render both panels', () => { + const topPanel =
; + const mainPanel =
; + const component = mountComponent({ topPanel, mainPanel }); + expect(component.contains(topPanel)).toBe(true); + expect(component.contains(mainPanel)).toBe(true); + }); + + it('should set the initial heights of both panels', () => { + const initialTopPanelHeight = 200; + const component = mountComponent({ initialTopPanelHeight }); + expectCorrectPanelSizes(component, containerHeight, initialTopPanelHeight); + }); + + it('should set the correct heights of both panels when the panels are resized', () => { + const initialTopPanelHeight = 200; + const component = mountComponent({ initialTopPanelHeight }); + expectCorrectPanelSizes(component, containerHeight, initialTopPanelHeight); + const newTopPanelSize = 30; + const onPanelSizeChange = component + .find('[data-test-subj="dscResizableContainer"]') + .at(0) + .prop('onPanelWidthChange') as Function; + act(() => { + onPanelSizeChange({ [topPanelId]: newTopPanelSize }); + }); + forceRender(component); + expectCorrectPanelSizes(component, containerHeight, containerHeight * (newTopPanelSize / 100)); + }); + + it('should maintain the height of the top panel and resize the main panel when the container height changes', () => { + const initialTopPanelHeight = 200; + const component = mountComponent({ initialTopPanelHeight }); + expectCorrectPanelSizes(component, containerHeight, initialTopPanelHeight); + const newContainerHeight = 2000; + jest.spyOn(eui, 'useResizeObserver').mockReturnValue({ height: newContainerHeight, width: 0 }); + forceRender(component); + expectCorrectPanelSizes(component, newContainerHeight, initialTopPanelHeight); + }); + + it('should resize the top panel once the main panel is at its minimum height', () => { + const initialTopPanelHeight = 500; + const minTopPanelHeight = 100; + const minMainPanelHeight = 100; + const component = mountComponent({ + initialTopPanelHeight, + minTopPanelHeight, + minMainPanelHeight, + }); + expectCorrectPanelSizes(component, containerHeight, initialTopPanelHeight); + const newContainerHeight = 400; + jest.spyOn(eui, 'useResizeObserver').mockReturnValue({ height: newContainerHeight, width: 0 }); + forceRender(component); + expectCorrectPanelSizes(component, newContainerHeight, newContainerHeight - minMainPanelHeight); + jest.spyOn(eui, 'useResizeObserver').mockReturnValue({ height: containerHeight, width: 0 }); + forceRender(component); + expectCorrectPanelSizes(component, containerHeight, initialTopPanelHeight); + }); + + it('should maintain the minimum heights of both panels when the container is too small to fit them', () => { + const initialTopPanelHeight = 500; + const minTopPanelHeight = 100; + const minMainPanelHeight = 150; + const component = mountComponent({ + initialTopPanelHeight, + minTopPanelHeight, + minMainPanelHeight, + }); + expectCorrectPanelSizes(component, containerHeight, initialTopPanelHeight); + const newContainerHeight = 200; + jest.spyOn(eui, 'useResizeObserver').mockReturnValue({ height: newContainerHeight, width: 0 }); + forceRender(component); + expect(component.find('[data-test-subj="dscResizablePanelTop"]').at(0).prop('size')).toBe( + (minTopPanelHeight / newContainerHeight) * 100 + ); + expect(component.find('[data-test-subj="dscResizablePanelMain"]').at(0).prop('size')).toBe( + (minMainPanelHeight / newContainerHeight) * 100 + ); + jest.spyOn(eui, 'useResizeObserver').mockReturnValue({ height: containerHeight, width: 0 }); + forceRender(component); + expectCorrectPanelSizes(component, containerHeight, initialTopPanelHeight); + }); + + it('should blur the resize button after a resize', async () => { + const attachTo = document.createElement('div'); + document.body.appendChild(attachTo); + const component = mountComponent({ attachTo }); + const wrapper = component.find('[data-test-subj="dscResizableContainerWrapper"]'); + const resizeButton = component.find('button[data-test-subj="dsc-resizable-button"]'); + const resizeButtonInner = component.find('[data-test-subj="dscResizableButtonInner"]'); + const mouseEvent = { + pageX: 0, + pageY: 0, + clientX: 0, + clientY: 0, + }; + resizeButtonInner.simulate('mousedown', mouseEvent); + resizeButton.simulate('mousedown', mouseEvent); + (resizeButton.getDOMNode() as HTMLElement).focus(); + wrapper.simulate('mouseup', mouseEvent); + resizeButton.simulate('click', mouseEvent); + expect(resizeButton.getDOMNode()).toHaveFocus(); + await waitFor(() => { + expect(resizeButton.getDOMNode()).not.toHaveFocus(); + }); + }); +}); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_panels_resizable.tsx b/src/plugins/discover/public/application/main/components/layout/discover_panels_resizable.tsx new file mode 100644 index 0000000000000..88a92b4380b76 --- /dev/null +++ b/src/plugins/discover/public/application/main/components/layout/discover_panels_resizable.tsx @@ -0,0 +1,178 @@ +/* + * Copyright 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 { EuiResizableContainer, useGeneratedHtmlId, useResizeObserver } from '@elastic/eui'; +import { css } from '@emotion/react'; +import React, { ReactElement, RefObject, useCallback, useEffect, useState } from 'react'; + +const percentToPixels = (containerHeight: number, percentage: number) => + Math.round(containerHeight * (percentage / 100)); + +const pixelsToPercent = (containerHeight: number, pixels: number) => + +((pixels / containerHeight) * 100).toFixed(4); + +export const DiscoverPanelsResizable = ({ + className, + resizeRef, + initialTopPanelHeight, + minTopPanelHeight, + minMainPanelHeight, + topPanel, + mainPanel, +}: { + className?: string; + resizeRef: RefObject; + initialTopPanelHeight: number; + minTopPanelHeight: number; + minMainPanelHeight: number; + topPanel: ReactElement; + mainPanel: ReactElement; +}) => { + const topPanelId = useGeneratedHtmlId({ prefix: 'topPanel' }); + const { height: containerHeight } = useResizeObserver(resizeRef.current); + const [topPanelHeight, setTopPanelHeight] = useState(initialTopPanelHeight); + const [panelSizes, setPanelSizes] = useState({ topPanelSize: 0, mainPanelSize: 0 }); + + // EuiResizableContainer doesn't work properly when used with react-reverse-portal and + // will cancel the resize. To work around this we keep track of when resizes start and + // end to toggle the rendering of a transparent overlay which prevents the cancellation. + // EUI issue: https://github.com/elastic/eui/issues/6199 + const [resizeWithPortalsHackIsResizing, setResizeWithPortalsHackIsResizing] = useState(false); + const enableResizeWithPortalsHack = () => setResizeWithPortalsHackIsResizing(true); + const disableResizeWithPortalsHack = () => setResizeWithPortalsHackIsResizing(false); + const resizeWithPortalsHackFillCss = css` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + `; + const resizeWithPortalsHackButtonCss = css` + z-index: 3; + `; + const resizeWithPortalsHackButtonInnerCss = css` + ${resizeWithPortalsHackFillCss} + z-index: 1; + `; + const resizeWithPortalsHackOverlayCss = css` + ${resizeWithPortalsHackFillCss} + z-index: 2; + `; + + // Instead of setting the panel sizes directly, we convert the top panel height + // from a percentage of the container height to a pixel value. This will trigger + // the effect below to update the panel sizes. + const onPanelSizeChange = useCallback( + ({ [topPanelId]: topPanelSize }: { [key: string]: number }) => { + setTopPanelHeight(percentToPixels(containerHeight, topPanelSize)); + }, + [containerHeight, topPanelId] + ); + + // This effect will update the panel sizes based on the top panel height whenever + // it or the container height changes. This allows us to keep the height of the + // top panel panel fixed when the window is resized. + useEffect(() => { + if (!containerHeight) { + return; + } + + let topPanelSize: number; + let mainPanelSize: number; + + // If the container height is less than the minimum main content height + // plus the current top panel height, then we need to make some adjustments. + if (containerHeight < minMainPanelHeight + topPanelHeight) { + const newTopPanelHeight = containerHeight - minMainPanelHeight; + + // Try to make the top panel height fit within the container, but if it + // doesn't then just use the minimum heights. + if (newTopPanelHeight < minTopPanelHeight) { + topPanelSize = pixelsToPercent(containerHeight, minTopPanelHeight); + mainPanelSize = pixelsToPercent(containerHeight, minMainPanelHeight); + } else { + topPanelSize = pixelsToPercent(containerHeight, newTopPanelHeight); + mainPanelSize = 100 - topPanelSize; + } + } else { + topPanelSize = pixelsToPercent(containerHeight, topPanelHeight); + mainPanelSize = 100 - topPanelSize; + } + + setPanelSizes({ topPanelSize, mainPanelSize }); + }, [containerHeight, topPanelHeight, minTopPanelHeight, minMainPanelHeight]); + + const onResizeEnd = () => { + // We don't want the resize button to retain focus after the resize is complete, + // but EuiResizableContainer will force focus it onClick. To work around this we + // use setTimeout to wait until after onClick has been called before blurring. + if (resizeWithPortalsHackIsResizing && document.activeElement instanceof HTMLElement) { + const button = document.activeElement; + setTimeout(() => { + button.blur(); + }); + } + + disableResizeWithPortalsHack(); + }; + + return ( +
+ + {(EuiResizablePanel, EuiResizableButton) => ( + <> + + {topPanel} + + + + + + {mainPanel} + + {resizeWithPortalsHackIsResizing ? ( +
+ ) : ( + <> + )} + + )} + +
+ ); +}; diff --git a/src/plugins/discover/public/application/main/components/sidebar/lib/visualize_trigger_utils.ts b/src/plugins/discover/public/application/main/components/sidebar/lib/visualize_trigger_utils.ts index 81f097aacf707..005accf3021f3 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/lib/visualize_trigger_utils.ts +++ b/src/plugins/discover/public/application/main/components/sidebar/lib/visualize_trigger_utils.ts @@ -15,6 +15,7 @@ import { import type { DataViewField, DataView } from '@kbn/data-views-plugin/public'; import { KBN_FIELD_TYPES } from '@kbn/data-plugin/public'; import { getUiActions } from '../../../../../kibana_services'; +import { PLUGIN_ID } from '../../../../../../common'; export function getTriggerConstant(type: string) { return type === KBN_FIELD_TYPES.GEO_POINT || type === KBN_FIELD_TYPES.GEO_SHAPE @@ -53,6 +54,7 @@ export function triggerVisualizeActions( dataViewSpec: dataView.toSpec(false), fieldName: field.name, contextualFields, + originatingApp: PLUGIN_ID, }; getUiActions().getTrigger(trigger).exec(triggerOptions); } diff --git a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx index dc0b981273c72..d65debc53a0b8 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx @@ -53,7 +53,7 @@ async function saveDataSource({ navigateTo(`/view/${encodeURIComponent(id)}`); } else { // Update defaults so that "reload saved query" functions correctly - state.resetAppState(); + state.resetAppState(savedSearch); services.chrome.docTitle.change(savedSearch.title!); setBreadcrumbsTitle( 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 696284852c15c..d5f693d7f5fb0 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -8,7 +8,6 @@ import React, { useEffect, useState, memo, useCallback } from 'react'; import { useParams, useHistory } from 'react-router-dom'; import { DataViewListItem } from '@kbn/data-plugin/public'; -import { ISearchSource } from '@kbn/data-plugin/public'; import { DataViewSavedObjectConflictError } from '@kbn/data-views-plugin/public'; import { redirectWhenMissing } from '@kbn/kibana-utils-plugin/public'; import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; @@ -70,7 +69,7 @@ export function DiscoverMainRoute(props: Props) { }); const loadDefaultOrCurrentDataView = useCallback( - async (searchSource: ISearchSource) => { + async (nextSavedSearch: SavedSearch) => { try { const hasUserDataViewValue = await data.dataViews.hasData .hasUserDataView() @@ -92,12 +91,12 @@ export function DiscoverMainRoute(props: Props) { return; } - const { appStateContainer } = getState({ history, uiSettings: config }); + const { appStateContainer } = getState({ history, savedSearch: nextSavedSearch, services }); const { index } = appStateContainer.getState(); const ip = await loadDataView(data.dataViews, config, index); const ipList = ip.list; - const dataViewData = resolveDataView(ip, searchSource, toastNotifications); + const dataViewData = resolveDataView(ip, nextSavedSearch.searchSource, toastNotifications); await data.dataViews.refreshFields(dataViewData); setDataViewList(ipList); @@ -106,7 +105,7 @@ export function DiscoverMainRoute(props: Props) { setError(e); } }, - [config, data.dataViews, history, isDev, toastNotifications] + [config, data.dataViews, history, isDev, toastNotifications, services] ); const loadSavedSearch = useCallback(async () => { @@ -118,7 +117,7 @@ export function DiscoverMainRoute(props: Props) { savedObjectsTagging: services.savedObjectsTagging, }); - const currentDataView = await loadDefaultOrCurrentDataView(currentSavedSearch.searchSource); + const currentDataView = await loadDefaultOrCurrentDataView(currentSavedSearch); if (!currentDataView) { return; diff --git a/src/plugins/discover/public/application/main/hooks/use_discover_state.ts b/src/plugins/discover/public/application/main/hooks/use_discover_state.ts index c52dd7b1635cf..8ad0d7ff7b590 100644 --- a/src/plugins/discover/public/application/main/hooks/use_discover_state.ts +++ b/src/plugins/discover/public/application/main/hooks/use_discover_state.ts @@ -44,7 +44,7 @@ export function useDiscoverState({ setExpandedDoc: (doc?: DataTableRecord) => void; dataViewList: DataViewListItem[]; }) { - const { uiSettings, data, filterManager, dataViews, storage } = services; + const { uiSettings, data, filterManager, dataViews } = services; const useNewFieldsApi = useMemo(() => !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [uiSettings]); const { timefilter } = data.query.timefilter; @@ -60,19 +60,11 @@ export function useDiscoverState({ const stateContainer = useMemo( () => getState({ - getStateDefaults: () => - getStateDefaults({ - config: uiSettings, - data, - savedSearch, - storage, - }), - storeInSessionStorage: uiSettings.get('state:storeInSessionStorage'), history, - toasts: services.core.notifications.toasts, - uiSettings, + savedSearch, + services, }), - [uiSettings, data, history, savedSearch, services.core.notifications.toasts, storage] + [history, savedSearch, services] ); const { appStateContainer } = stateContainer; @@ -231,10 +223,8 @@ export function useDiscoverState({ const newDataView = newSavedSearch.searchSource.getField('index') || dataView; newSavedSearch.searchSource.setField('index', newDataView); const newAppState = getStateDefaults({ - config: uiSettings, - data, savedSearch: newSavedSearch, - storage, + services, }); restoreStateFromSavedSearch({ @@ -245,7 +235,7 @@ export function useDiscoverState({ await stateContainer.replaceUrlAppState(newAppState); setState(newAppState); }, - [services, dataView, uiSettings, data, storage, stateContainer] + [services, dataView, stateContainer] ); /** diff --git a/src/plugins/discover/public/application/main/hooks/use_saved_search.test.ts b/src/plugins/discover/public/application/main/hooks/use_saved_search.test.ts index bacdbc906d724..34cdeb232be88 100644 --- a/src/plugins/discover/public/application/main/hooks/use_saved_search.test.ts +++ b/src/plugins/discover/public/application/main/hooks/use_saved_search.test.ts @@ -9,10 +9,9 @@ import { Subject } from 'rxjs'; import { renderHook } from '@testing-library/react-hooks'; import { createSearchSessionMock } from '../../../__mocks__/search_session'; import { discoverServiceMock } from '../../../__mocks__/services'; -import { savedSearchMock } from '../../../__mocks__/saved_search'; +import { savedSearchMock, savedSearchMockWithSQL } from '../../../__mocks__/saved_search'; import { RecordRawType, useSavedSearch } from './use_saved_search'; -import { getState, AppState } from '../services/discover_state'; -import { uiSettingsMock } from '../../../__mocks__/ui_settings'; +import { getState } from '../services/discover_state'; import { useDiscoverState } from './use_discover_state'; import { FetchStatus } from '../../types'; import { dataViewMock } from '../../../__mocks__/data_view'; @@ -25,9 +24,9 @@ describe('test useSavedSearch', () => { test('useSavedSearch return is valid', async () => { const { history, searchSessionManager } = createSearchSessionMock(); const stateContainer = getState({ - getStateDefaults: () => ({ index: 'the-data-view-id' }), + savedSearch: savedSearchMock, + services: discoverServiceMock, history, - uiSettings: uiSettingsMock, }); const { result } = renderHook(() => { @@ -51,9 +50,9 @@ describe('test useSavedSearch', () => { test('refetch$ triggers a search', async () => { const { history, searchSessionManager } = createSearchSessionMock(); const stateContainer = getState({ - getStateDefaults: () => ({ index: 'the-data-view-id' }), + savedSearch: savedSearchMock, + services: discoverServiceMock, history, - uiSettings: uiSettingsMock, }); discoverServiceMock.data.query.timefilter.timefilter.getTime = jest.fn(() => { @@ -95,9 +94,9 @@ describe('test useSavedSearch', () => { test('reset sets back to initial state', async () => { const { history, searchSessionManager } = createSearchSessionMock(); const stateContainer = getState({ - getStateDefaults: () => ({ index: 'the-data-view-id' }), + savedSearch: savedSearchMock, + services: discoverServiceMock, history, - uiSettings: uiSettingsMock, }); discoverServiceMock.data.query.timefilter.timefilter.getTime = jest.fn(() => { @@ -139,21 +138,17 @@ describe('test useSavedSearch', () => { test('useSavedSearch returns plain record raw type', async () => { const { history, searchSessionManager } = createSearchSessionMock(); const stateContainer = getState({ - getStateDefaults: () => - ({ - index: 'the-index-pattern-id', - query: { sql: 'SELECT * FROM test' }, - } as unknown as AppState), + savedSearch: savedSearchMockWithSQL, + services: discoverServiceMock, history, - uiSettings: uiSettingsMock, }); const { result } = renderHook(() => { return useSavedSearch({ initialFetchStatus: FetchStatus.LOADING, - savedSearch: savedSearchMock, + savedSearch: savedSearchMockWithSQL, searchSessionManager, - searchSource: savedSearchMock.searchSource.createCopy(), + searchSource: savedSearchMockWithSQL.searchSource.createCopy(), services: discoverServiceMock, stateContainer, useNewFieldsApi: true, diff --git a/src/plugins/discover/public/application/main/hooks/use_search_session.test.ts b/src/plugins/discover/public/application/main/hooks/use_search_session.test.ts index bc9af1001aa77..f29d1414b51d3 100644 --- a/src/plugins/discover/public/application/main/hooks/use_search_session.test.ts +++ b/src/plugins/discover/public/application/main/hooks/use_search_session.test.ts @@ -12,15 +12,14 @@ import { createSearchSessionMock } from '../../../__mocks__/search_session'; import { discoverServiceMock } from '../../../__mocks__/services'; import { savedSearchMock } from '../../../__mocks__/saved_search'; import { getState } from '../services/discover_state'; -import { uiSettingsMock } from '../../../__mocks__/ui_settings'; describe('test useSearchSession', () => { test('getting the next session id', async () => { const { history } = createSearchSessionMock(); const stateContainer = getState({ - getStateDefaults: () => ({ index: 'test' }), + savedSearch: savedSearchMock, history, - uiSettings: uiSettingsMock, + services: discoverServiceMock, }); const nextId = 'id'; diff --git a/src/plugins/discover/public/application/main/services/discover_state.test.ts b/src/plugins/discover/public/application/main/services/discover_state.test.ts index ccec7582ce8d3..aef263b23cec9 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.test.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { IUiSettingsClient } from '@kbn/core/public'; import { getState, GetStateReturn, @@ -14,17 +13,14 @@ import { } from './discover_state'; import { createBrowserHistory, History } from 'history'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import type { SavedSearch } from '@kbn/saved-search-plugin/public'; -import { SEARCH_FIELDS_FROM_SOURCE } from '../../../../common'; +import type { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public'; +import { savedSearchMock, savedSearchMockWithTimeField } from '../../../__mocks__/saved_search'; +import { discoverServiceMock } from '../../../__mocks__/services'; let history: History; let state: GetStateReturn; const getCurrentUrl = () => history.createHref(history.location); -const uiSettingsMock = { - get: (key: string) => (key === SEARCH_FIELDS_FROM_SOURCE ? true : ['_source']) as unknown as T, -} as IUiSettingsClient; - describe('Test discover state', () => { let stopSync = () => {}; @@ -32,9 +28,9 @@ describe('Test discover state', () => { history = createBrowserHistory(); history.push('/'); state = getState({ - getStateDefaults: () => ({ index: 'test' }), + savedSearch: savedSearchMock, + services: discoverServiceMock, history, - uiSettings: uiSettingsMock, }); await state.replaceUrlAppState({}); stopSync = state.startSync(); @@ -46,7 +42,9 @@ describe('Test discover state', () => { test('setting app state and syncing to URL', async () => { state.setAppState({ index: 'modified' }); state.flushToUrl(); - expect(getCurrentUrl()).toMatchInlineSnapshot(`"/#?_a=(index:modified)"`); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_a=(columns:!(default_column),index:modified,interval:auto,sort:!())"` + ); }); test('changing URL to be propagated to appState', async () => { @@ -60,11 +58,9 @@ describe('Test discover state', () => { test('URL navigation to url without _a, state should not change', async () => { history.push('/#?_a=(index:modified)'); history.push('/'); - expect(state.appStateContainer.getState()).toMatchInlineSnapshot(` - Object { - "index": "modified", - } - `); + expect(state.appStateContainer.getState()).toEqual({ + index: 'modified', + }); }); test('isAppStateDirty returns whether the current state has changed', async () => { @@ -89,46 +85,45 @@ describe('Test discover state', () => { }); }); describe('Test discover initial state sort handling', () => { - test('Non-empty sort in URL should not fallback to state defaults', async () => { + test('Non-empty sort in URL should not be overwritten by saved search sort', async () => { history = createBrowserHistory(); history.push('/#?_a=(sort:!(!(order_date,desc)))'); state = getState({ - getStateDefaults: () => ({ sort: [['fallback', 'desc']] }), + savedSearch: { ...savedSearchMock, ...{ sort: [['bytes', 'desc']] } }, + services: discoverServiceMock, history, - uiSettings: uiSettingsMock, }); await state.replaceUrlAppState({}); const stopSync = state.startSync(); - expect(state.appStateContainer.getState().sort).toMatchInlineSnapshot(` - Array [ - Array [ - "order_date", - "desc", - ], - ] - `); + expect(state.appStateContainer.getState().sort).toEqual([['order_date', 'desc']]); stopSync(); }); - test('Empty sort in URL should allow fallback state defaults', async () => { + test('Empty sort in URL should use saved search sort for state', async () => { history = createBrowserHistory(); history.push('/#?_a=(sort:!())'); - + const nextSavedSearch = { ...savedSearchMock, ...{ sort: [['bytes', 'desc']] as SortOrder[] } }; state = getState({ - getStateDefaults: () => ({ sort: [['fallback', 'desc']] }), + savedSearch: nextSavedSearch, + services: discoverServiceMock, history, - uiSettings: uiSettingsMock, }); await state.replaceUrlAppState({}); const stopSync = state.startSync(); - expect(state.appStateContainer.getState().sort).toMatchInlineSnapshot(` - Array [ - Array [ - "fallback", - "desc", - ], - ] - `); + expect(state.appStateContainer.getState().sort).toEqual([['bytes', 'desc']]); + stopSync(); + }); + test('Empty sort in URL and saved search should sort by timestamp', async () => { + history = createBrowserHistory(); + history.push('/#?_a=(sort:!())'); + state = getState({ + savedSearch: savedSearchMockWithTimeField, + services: discoverServiceMock, + history, + }); + await state.replaceUrlAppState({}); + const stopSync = state.startSync(); + expect(state.appStateContainer.getState().sort).toEqual([['timestamp', 'desc']]); stopSync(); }); }); @@ -140,20 +135,17 @@ describe('Test discover state with legacy migration', () => { "/#?_a=(query:(query_string:(analyze_wildcard:!t,query:'type:nice%20name:%22yeah%22')))" ); state = getState({ - getStateDefaults: () => ({ index: 'test' }), + savedSearch: savedSearchMock, + services: discoverServiceMock, history, - uiSettings: uiSettingsMock, }); - expect(state.appStateContainer.getState()).toMatchInlineSnapshot(` + expect(state.appStateContainer.getState().query).toMatchInlineSnapshot(` Object { - "index": "test", + "language": "lucene", "query": Object { - "language": "lucene", - "query": Object { - "query_string": Object { - "analyze_wildcard": true, - "query": "type:nice name:\\"yeah\\"", - }, + "query_string": Object { + "analyze_wildcard": true, + "query": "type:nice name:\\"yeah\\"", }, }, } @@ -167,8 +159,9 @@ describe('createSearchSessionRestorationDataProvider', () => { const searchSessionInfoProvider = createSearchSessionRestorationDataProvider({ data: mockDataPlugin, appStateContainer: getState({ - history: createBrowserHistory(), - uiSettings: uiSettingsMock, + savedSearch: savedSearchMock, + services: discoverServiceMock, + history, }).appStateContainer, getSavedSearch: () => mockSavedSearch, }); @@ -217,12 +210,10 @@ describe('createSearchSessionRestorationDataProvider', () => { test('restoreState has paused autoRefresh', async () => { const { initialState, restoreState } = await searchSessionInfoProvider.getLocatorData(); expect(initialState.refreshInterval).toBe(undefined); - expect(restoreState.refreshInterval).toMatchInlineSnapshot(` - Object { - "pause": true, - "value": 0, - } - `); + expect(restoreState.refreshInterval).toEqual({ + pause: true, + value: 0, + }); }); }); }); diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts index 568a219b952e7..611b59eacdc79 100644 --- a/src/plugins/discover/public/application/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/main/services/discover_state.ts @@ -9,7 +9,6 @@ import { cloneDeep, isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; import { History } from 'history'; -import { NotificationsStart, IUiSettingsClient } from '@kbn/core/public'; import { Filter, FilterStateStore, @@ -37,6 +36,8 @@ import { } from '@kbn/data-plugin/public'; import { DataView } from '@kbn/data-views-plugin/public'; import { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { getStateDefaults } from '../utils/get_state_defaults'; +import { DiscoverServices } from '../../../build_services'; import { DiscoverGridSettings } from '../../../components/discover_grid/types'; import { handleSourceColumnState } from '../../../utils/state_helpers'; import { DISCOVER_APP_LOCATOR, DiscoverAppLocatorParams } from '../../../locator'; @@ -107,30 +108,18 @@ export interface AppStateUrl extends Omit { } interface GetStateParams { - /** - * Default state used for merging with with URL state to get the initial state - */ - getStateDefaults?: () => AppState; - /** - * Determins the use of long vs. short/hashed urls - */ - storeInSessionStorage?: boolean; /** * Browser history */ history: History; - /** - * Core's notifications.toasts service - * In case it is passed in, - * kbnUrlStateStorage will use it notifying about inner errors + * The current savedSearch */ - toasts?: NotificationsStart['toasts']; - + savedSearch: SavedSearch; /** * core ui settings service */ - uiSettings: IUiSettingsClient; + services: DiscoverServices; } export interface GetStateReturn { @@ -179,9 +168,9 @@ export interface GetStateReturn { */ isAppStateDirty: () => boolean; /** - * Reset AppState to default, discarding all changes + * Reset AppState by the given savedSearch discarding all changes */ - resetAppState: () => void; + resetAppState: (nextSavedSearch: SavedSearch) => void; /** * Pause the auto refresh interval without pushing an entry to history */ @@ -195,14 +184,13 @@ const GLOBAL_STATE_URL_KEY = '_g'; * Builds and returns appState and globalState containers and helper functions * Used to sync URL with UI state */ -export function getState({ - getStateDefaults, - storeInSessionStorage = false, - history, - toasts, - uiSettings, -}: GetStateParams): GetStateReturn { - const defaultAppState = getStateDefaults ? getStateDefaults() : {}; +export function getState({ history, savedSearch, services }: GetStateParams): GetStateReturn { + const storeInSessionStorage = services.uiSettings.get('state:storeInSessionStorage'); + const toasts = services.core.notifications.toasts; + const defaultAppState = getStateDefaults({ + savedSearch, + services, + }); const stateStorage = createKbnUrlStateStorage({ useHash: storeInSessionStorage, history, @@ -216,7 +204,7 @@ export function getState({ ...defaultAppState, ...appStateFromUrl, }, - uiSettings + services.uiSettings ); // todo filter source depending on fields fetching flag (if no columns remain and source fetching is enabled, use default columns) @@ -275,10 +263,10 @@ export function getState({ resetInitialAppState: () => { initialAppState = appStateContainer.getState(); }, - resetAppState: () => { + resetAppState: (nextSavedSearch: SavedSearch) => { const defaultState = handleSourceColumnState( - getStateDefaults ? getStateDefaults() : {}, - uiSettings + getStateDefaults({ savedSearch: nextSavedSearch, services }), + services.uiSettings ); setState(appStateContainerModified, defaultState); }, diff --git a/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts b/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts index 17cb547055a87..1d5cf07446d60 100644 --- a/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts +++ b/src/plugins/discover/public/application/main/utils/get_state_defaults.test.ts @@ -7,23 +7,18 @@ */ import { getStateDefaults } from './get_state_defaults'; -import { createSearchSourceMock, dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { uiSettingsMock } from '../../../__mocks__/ui_settings'; +import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks'; import { dataViewWithTimefieldMock } from '../../../__mocks__/data_view_with_timefield'; import { savedSearchMock } from '../../../__mocks__/saved_search'; import { dataViewMock } from '../../../__mocks__/data_view'; import { discoverServiceMock } from '../../../__mocks__/services'; describe('getStateDefaults', () => { - const storage = discoverServiceMock.storage; - test('data view with timefield', () => { savedSearchMock.searchSource = createSearchSourceMock({ index: dataViewWithTimefieldMock }); const actual = getStateDefaults({ - config: uiSettingsMock, - data: dataPluginMock.createStartContract(), + services: discoverServiceMock, savedSearch: savedSearchMock, - storage, }); expect(actual).toMatchInlineSnapshot(` Object { @@ -55,10 +50,8 @@ describe('getStateDefaults', () => { savedSearchMock.searchSource = createSearchSourceMock({ index: dataViewMock }); const actual = getStateDefaults({ - config: uiSettingsMock, - data: dataPluginMock.createStartContract(), + services: discoverServiceMock, savedSearch: savedSearchMock, - storage, }); expect(actual).toMatchInlineSnapshot(` Object { diff --git a/src/plugins/discover/public/application/main/utils/get_state_defaults.ts b/src/plugins/discover/public/application/main/utils/get_state_defaults.ts index f058c473127d4..a4a43fa342715 100644 --- a/src/plugins/discover/public/application/main/utils/get_state_defaults.ts +++ b/src/plugins/discover/public/application/main/utils/get_state_defaults.ts @@ -8,9 +8,8 @@ import { cloneDeep, isEqual } from 'lodash'; import { IUiSettingsClient } from '@kbn/core/public'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { Storage } from '@kbn/kibana-utils-plugin/public'; import { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { DiscoverServices } from '../../../build_services'; import { getDefaultSort, getSortArray } from '../../../utils/sorting'; import { DEFAULT_COLUMNS_SETTING, @@ -22,33 +21,33 @@ import { import { AppState } from '../services/discover_state'; import { CHART_HIDDEN_KEY } from '../components/chart/discover_chart'; -function getDefaultColumns(savedSearch: SavedSearch, config: IUiSettingsClient) { +function getDefaultColumns(savedSearch: SavedSearch, uiSettings: IUiSettingsClient) { if (savedSearch.columns && savedSearch.columns.length > 0) { return [...savedSearch.columns]; } - if (config.get(SEARCH_FIELDS_FROM_SOURCE) && isEqual(config.get(DEFAULT_COLUMNS_SETTING), [])) { + if ( + uiSettings.get(SEARCH_FIELDS_FROM_SOURCE) && + isEqual(uiSettings.get(DEFAULT_COLUMNS_SETTING), []) + ) { return ['_source']; } - return [...config.get(DEFAULT_COLUMNS_SETTING)]; + return [...uiSettings.get(DEFAULT_COLUMNS_SETTING)]; } export function getStateDefaults({ - config, - data, savedSearch, - storage, + services, }: { - config: IUiSettingsClient; - data: DataPublicPluginStart; savedSearch: SavedSearch; - storage: Storage; + services: DiscoverServices; }) { const { searchSource } = savedSearch; + const { data, uiSettings, storage } = services; const dataView = searchSource.getField('index'); const query = searchSource.getField('query') || data.query.queryString.getDefaultQuery(); const sort = getSortArray(savedSearch.sort ?? [], dataView!); - const columns = getDefaultColumns(savedSearch, config); + const columns = getDefaultColumns(savedSearch, uiSettings); const chartHidden = storage.get(CHART_HIDDEN_KEY); const defaultState: AppState = { @@ -56,8 +55,8 @@ export function getStateDefaults({ sort: !sort.length ? getDefaultSort( dataView, - config.get(SORT_DEFAULT_ORDER_SETTING, 'desc'), - config.get(DOC_HIDE_TIME_COLUMN_SETTING, false) + uiSettings.get(SORT_DEFAULT_ORDER_SETTING, 'desc'), + uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false) ) : sort, columns, diff --git a/src/plugins/discover/public/components/view_mode_toggle/_index.scss b/src/plugins/discover/public/components/view_mode_toggle/_index.scss deleted file mode 100644 index a76c3453de32a..0000000000000 --- a/src/plugins/discover/public/components/view_mode_toggle/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'view_mode_toggle'; diff --git a/src/plugins/discover/public/components/view_mode_toggle/_view_mode_toggle.scss b/src/plugins/discover/public/components/view_mode_toggle/_view_mode_toggle.scss deleted file mode 100644 index 1009ab0511957..0000000000000 --- a/src/plugins/discover/public/components/view_mode_toggle/_view_mode_toggle.scss +++ /dev/null @@ -1,12 +0,0 @@ -.dscViewModeToggle { - padding-right: $euiSize; -} - -.fieldStatsButton { - display: flex; - align-items: center; -} - -.fieldStatsBetaBadge { - margin-left: $euiSizeXS; -} diff --git a/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.test.tsx b/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.test.tsx new file mode 100644 index 0000000000000..450d7c2816d7d --- /dev/null +++ b/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.test.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiTab } from '@elastic/eui'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import React from 'react'; +import { VIEW_MODE } from './constants'; +import { DocumentViewModeToggle } from './view_mode_toggle'; + +describe('Document view mode toggle component', () => { + const mountComponent = ({ + showFieldStatistics = true, + viewMode = VIEW_MODE.DOCUMENT_LEVEL, + setDiscoverViewMode = jest.fn(), + } = {}) => { + const serivces = { + uiSettings: { + get: () => showFieldStatistics, + }, + }; + + return mountWithIntl( + + + + ); + }; + + it('should render if SHOW_FIELD_STATISTICS is true', () => { + const component = mountComponent(); + expect(component.isEmptyRender()).toBe(false); + }); + + it('should not render if SHOW_FIELD_STATISTICS is false', () => { + const component = mountComponent({ showFieldStatistics: false }); + expect(component.isEmptyRender()).toBe(true); + }); + + it('should set the view mode to VIEW_MODE.DOCUMENT_LEVEL when dscViewModeDocumentButton is clicked', () => { + const setDiscoverViewMode = jest.fn(); + const component = mountComponent({ setDiscoverViewMode }); + component.find('[data-test-subj="dscViewModeDocumentButton"]').at(0).simulate('click'); + expect(setDiscoverViewMode).toHaveBeenCalledWith(VIEW_MODE.DOCUMENT_LEVEL); + }); + + it('should set the view mode to VIEW_MODE.AGGREGATED_LEVEL when dscViewModeFieldStatsButton is clicked', () => { + const setDiscoverViewMode = jest.fn(); + const component = mountComponent({ setDiscoverViewMode }); + component.find('[data-test-subj="dscViewModeFieldStatsButton"]').at(0).simulate('click'); + expect(setDiscoverViewMode).toHaveBeenCalledWith(VIEW_MODE.AGGREGATED_LEVEL); + }); + + it('should select the Documents tab if viewMode is VIEW_MODE.DOCUMENT_LEVEL', () => { + const component = mountComponent(); + expect(component.find(EuiTab).at(0).prop('isSelected')).toBe(true); + }); + + it('should select the Field statistics tab if viewMode is VIEW_MODE.AGGREGATED_LEVEL', () => { + const component = mountComponent({ viewMode: VIEW_MODE.AGGREGATED_LEVEL }); + expect(component.find(EuiTab).at(1).prop('isSelected')).toBe(true); + }); +}); diff --git a/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.tsx b/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.tsx index af48673bf31be..f499df891ef34 100644 --- a/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.tsx +++ b/src/plugins/discover/public/components/view_mode_toggle/view_mode_toggle.tsx @@ -6,12 +6,22 @@ * Side Public License, v 1. */ -import { EuiButtonGroup, EuiBetaBadge } from '@elastic/eui'; -import React, { useMemo } from 'react'; -import { i18n } from '@kbn/i18n'; +import { + EuiTabs, + EuiTab, + useEuiPaddingSize, + EuiBetaBadge, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { i18n } from '@kbn/i18n'; import { VIEW_MODE } from './constants'; -import './_index.scss'; +import { SHOW_FIELD_STATISTICS } from '../../../common'; +import { useDiscoverServices } from '../../hooks/use_discover_services'; export const DocumentViewModeToggle = ({ viewMode, @@ -20,23 +30,47 @@ export const DocumentViewModeToggle = ({ viewMode: VIEW_MODE; setDiscoverViewMode: (viewMode: VIEW_MODE) => void; }) => { - const toggleButtons = useMemo( - () => [ - { - id: VIEW_MODE.DOCUMENT_LEVEL, - label: i18n.translate('discover.viewModes.document.label', { - defaultMessage: 'Documents', - }), - 'data-test-subj': 'dscViewModeDocumentButton', - }, - { - id: VIEW_MODE.AGGREGATED_LEVEL, - label: ( -
+ const { uiSettings } = useDiscoverServices(); + + const tabsCss = css` + padding: 0 ${useEuiPaddingSize('s')}; + background-color: ${euiThemeVars.euiPageBackgroundColor}; + `; + + const badgeCellCss = css` + margin-left: ${useEuiPaddingSize('s')}; + `; + + const showViewModeToggle = uiSettings.get(SHOW_FIELD_STATISTICS) ?? false; + + if (!showViewModeToggle) { + return null; + } + + return ( + + setDiscoverViewMode(VIEW_MODE.DOCUMENT_LEVEL)} + className="dscViewModeToggle__tab" + data-test-subj="dscViewModeDocumentButton" + > + + + setDiscoverViewMode(VIEW_MODE.AGGREGATED_LEVEL)} + className="dscViewModeToggle__tab" + data-test-subj="dscViewModeFieldStatsButton" + > + + + + -
- ), - }, - ], - [] - ); - - return ( - setDiscoverViewMode(id as VIEW_MODE)} - data-test-subj={'dscViewModeToggle'} - /> + + + + ); }; diff --git a/src/plugins/event_annotation/common/fetch_event_annotations/request_event_annotations.ts b/src/plugins/event_annotation/common/fetch_event_annotations/request_event_annotations.ts index e7127bf500ab5..f6eaf0b0e4bbd 100644 --- a/src/plugins/event_annotation/common/fetch_event_annotations/request_event_annotations.ts +++ b/src/plugins/event_annotation/common/fetch_event_annotations/request_event_annotations.ts @@ -49,6 +49,7 @@ interface QueryGroup { timeField: string; dataView: DataView; allFields?: string[]; + ignoreGlobalFilters: boolean; } export function getTimeZone(uiSettings: IUiSettingsClient) { @@ -112,18 +113,20 @@ export const requestEventAnnotations = ( dataView, aggConfigs, timeFields, + ignoreGlobalFilters, }: { dataView: DataView; aggConfigs: AggConfigs; timeFields: string[]; + ignoreGlobalFilters: boolean; }) => lastValueFrom( handleRequest({ aggs: aggConfigs, indexPattern: dataView, timeFields, - filters: input?.filters, - query: input?.query as any, + filters: ignoreGlobalFilters ? undefined : input?.filters, + query: ignoreGlobalFilters ? undefined : (input?.query as any), timeRange: input?.timeRange, abortSignal, inspectorAdapters, @@ -182,7 +185,7 @@ const convertManualToDatatableRows = ( return datatableRows; }; -const prepareEsaggsForQueryGroups = async ( +const prepareEsaggsForQueryGroups = ( queryGroups: QueryGroup[], interval: string, aggs: AggsStart @@ -267,7 +270,12 @@ const prepareEsaggsForQueryGroups = async ( aggregations?.map((agg) => agg.value) ?? [] ); return { - esaggsParams: { dataView: group.dataView, aggConfigs, timeFields: [group.timeField] }, + esaggsParams: { + dataView: group.dataView, + aggConfigs, + timeFields: [group.timeField], + ignoreGlobalFilters: Boolean(group.ignoreGlobalFilters), + }, fieldsColIdMap: group.allFields?.reduce>( (acc, fieldName, i) => ({ @@ -306,7 +314,7 @@ function regroupForRequestOptimization( (dataView.timeFieldName || dataView.fields.find((field) => field.type === 'date' && field.displayName)?.name); - const key = `${g.dataView.value.id}-${timeField}`; + const key = `${g.dataView.value.id}-${timeField}-${Boolean(current.ignoreGlobalFilters)}`; const subGroup = acc[key] as QueryGroup; if (subGroup) { let allFields = [...(subGroup.allFields || []), ...(current.extraFields || [])]; @@ -334,6 +342,7 @@ function regroupForRequestOptimization( timeField: timeField!, allFields, annotations: [current], + ignoreGlobalFilters: Boolean(current.ignoreGlobalFilters), }, }; } diff --git a/src/plugins/event_annotation/common/query_point_event_annotation/index.ts b/src/plugins/event_annotation/common/query_point_event_annotation/index.ts index cb9ba882a9f89..c30ab9c4a8919 100644 --- a/src/plugins/event_annotation/common/query_point_event_annotation/index.ts +++ b/src/plugins/event_annotation/common/query_point_event_annotation/index.ts @@ -103,6 +103,13 @@ export const queryPointEventAnnotation: ExpressionFunctionDefinition< defaultMessage: `Switch to hide annotation`, }), }, + ignoreGlobalFilters: { + types: ['boolean'], + help: i18n.translate('eventAnnotation.queryAnnotation.args.ignoreGlobalFilters', { + defaultMessage: `Switch to ignore global filters for the annotation`, + }), + default: true, + }, }, fn: function fn(input: unknown, args: QueryPointEventAnnotationArgs) { return { diff --git a/src/plugins/event_annotation/common/query_point_event_annotation/types.ts b/src/plugins/event_annotation/common/query_point_event_annotation/types.ts index 592c50a0621ce..defefa11d7443 100644 --- a/src/plugins/event_annotation/common/query_point_event_annotation/types.ts +++ b/src/plugins/event_annotation/common/query_point_event_annotation/types.ts @@ -15,6 +15,7 @@ export type QueryPointEventAnnotationArgs = { timeField?: string; extraFields?: string[]; textField?: string; + ignoreGlobalFilters?: boolean; } & PointStyleProps; export type QueryPointEventAnnotationOutput = QueryPointEventAnnotationArgs & { diff --git a/src/plugins/event_annotation/common/types.ts b/src/plugins/event_annotation/common/types.ts index 76749bee5bc79..4868e861ff348 100644 --- a/src/plugins/event_annotation/common/types.ts +++ b/src/plugins/event_annotation/common/types.ts @@ -72,6 +72,7 @@ export type QueryPointEventAnnotationConfig = { timeField?: string; textField?: string; extraFields?: string[]; + ignoreGlobalFilters?: boolean; key: { type: 'point_in_time'; }; diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.test.ts b/src/plugins/event_annotation/public/event_annotation_service/service.test.ts index 0fea0e7c38ea7..5abed8ba618ba 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/service.test.ts +++ b/src/plugins/event_annotation/public/event_annotation_service/service.test.ts @@ -157,6 +157,7 @@ describe('Event Annotation Service', () => { icon: ['triangle'], textVisibility: [false], textField: [], + ignoreGlobalFilters: [false], filter: [ { chain: [ @@ -264,6 +265,7 @@ describe('Event Annotation Service', () => { icon: ['triangle'], textVisibility: [false], textField: [], + ignoreGlobalFilters: [false], filter: [ { chain: [ @@ -326,6 +328,7 @@ describe('Event Annotation Service', () => { icon: ['triangle'], textVisibility: [textVisibility], textField: expected ? [expected] : [], + ignoreGlobalFilters: [false], filter: [ { chain: [ diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.tsx b/src/plugins/event_annotation/public/event_annotation_service/service.tsx index 41bf12c288cc1..bb8c8d0d62ebd 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/service.tsx +++ b/src/plugins/event_annotation/public/event_annotation_service/service.tsx @@ -91,6 +91,7 @@ export function getEventAnnotationService(): EventAnnotationServiceType { textField, filter, extraFields, + ignoreGlobalFilters, } = annotation; expressions.push({ type: 'expression' as const, @@ -110,6 +111,7 @@ export function getEventAnnotationService(): EventAnnotationServiceType { textField: textVisibility && textField ? [textField] : [], filter: filter ? [queryToAst(filter)] : [], extraFields: extraFields || [], + ignoreGlobalFilters: [Boolean(ignoreGlobalFilters)], }, }, ], diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index 2244d3d5503e0..3311f42bff55d 100644 --- a/src/plugins/kibana_react/public/index.ts +++ b/src/plugins/kibana_react/public/index.ts @@ -41,9 +41,6 @@ export { useUiSetting, useUiSetting$ } from './ui_settings'; export { useExecutionContext } from './use_execution_context'; -export type { TableListViewProps, TableListViewState } from './table_list_view'; -export { TableListView } from './table_list_view'; - export type { ToolbarButtonProps } from './toolbar_button'; export { POSITIONS, WEIGHTS, TOOLBAR_BUTTON_SIZES, ToolbarButton } from './toolbar_button'; diff --git a/src/plugins/kibana_react/public/table_list_view/__snapshots__/table_list_view.test.tsx.snap b/src/plugins/kibana_react/public/table_list_view/__snapshots__/table_list_view.test.tsx.snap deleted file mode 100644 index 1f99e74ef97dc..0000000000000 --- a/src/plugins/kibana_react/public/table_list_view/__snapshots__/table_list_view.test.tsx.snap +++ /dev/null @@ -1,163 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TableListView render custom empty prompt 1`] = ` - - - -`; - -exports[`TableListView render default empty prompt 1`] = ` - - - -

- } - /> - -`; - -exports[`TableListView render default empty prompt with create action when createItem supplied 1`] = ` - - - - - } - title={ -

- -

- } - /> -
-`; - -exports[`TableListView render list view 1`] = ` - - test title - , - "rightSideItems": Array [ - undefined, - ], - } - } -> - - } - onChange={[Function]} - pagination={ - Object { - "pageIndex": 0, - "pageSize": 20, - "pageSizeOptions": Array [ - 10, - 20, - 50, - ], - "totalItemCount": 1, - } - } - responsive={true} - rowHeader="name" - search={ - Object { - "box": Object { - "data-test-subj": "tableListSearchBox", - "incremental": true, - }, - "defaultQuery": "", - "filters": Array [], - "onChange": [Function], - "toolsLeft": undefined, - } - } - tableCaption="test caption" - tableLayout="fixed" - /> - -`; diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx deleted file mode 100644 index bcb07cab0ae01..0000000000000 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx +++ /dev/null @@ -1,686 +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 { - EuiBasicTableColumn, - EuiButton, - EuiCallOut, - EuiConfirmModal, - EuiEmptyPrompt, - EuiInMemoryTable, - Pagination, - CriteriaWithPagination, - PropertySort, - Direction, - EuiLink, - EuiSpacer, - EuiTableActionsColumnType, - SearchFilterConfig, - EuiToolTip, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react'; -import type { IHttpFetchError } from '@kbn/core-http-browser'; -import type { ThemeServiceStart, ToastsStart, ApplicationStart } from '@kbn/core/public'; -import { debounce, keyBy, sortBy, uniq, get } from 'lodash'; -import React from 'react'; -import moment from 'moment'; -import { KibanaPageTemplate } from '../page_template'; -import { toMountPoint } from '../util'; - -export interface TableListViewProps { - createItem?(): void; - deleteItems?(items: V[]): Promise; - editItem?(item: V): void; - entityName: string; - entityNamePlural: string; - findItems(query: string): Promise<{ total: number; hits: V[] }>; - listingLimit: number; - initialFilter: string; - initialPageSize: number; - /** - * Should be an EuiEmptyPrompt (but TS doesn't support this typing) - */ - emptyPrompt?: JSX.Element; - tableColumns: Array>; - tableListTitle: string; - toastNotifications: ToastsStart; - /** - * Id of the heading element describing the table. This id will be used as `aria-labelledby` of the wrapper element. - * If the table is not empty, this component renders its own h1 element using the same id. - */ - headingId?: string; - /** - * Indicates which column should be used as the identifying cell in each row. - */ - rowHeader: string; - /** - * Describes the content of the table. If not specified, the caption will be "This table contains {itemCount} rows." - */ - tableCaption: string; - searchFilters?: SearchFilterConfig[]; - theme: ThemeServiceStart; - application: ApplicationStart; -} - -export interface TableListViewState { - items: V[]; - hasInitialFetchReturned: boolean; - hasUpdatedAtMetadata: boolean | null; - isFetchingItems: boolean; - isDeletingItems: boolean; - showDeleteModal: boolean; - showLimitError: boolean; - fetchError?: IHttpFetchError; - filter: string; - selectedIds: string[]; - totalItems: number; - pagination: Pagination; - tableSort?: { - field: keyof V; - direction: Direction; - }; -} - -// saved object client does not support sorting by title because title is only mapped as analyzed -// the legacy implementation got around this by pulling `listingLimit` items and doing client side sorting -// and not supporting server-side paging. -// This component does not try to tackle these problems (yet) and is just feature matching the legacy component -// TODO support server side sorting/paging once title and description are sortable on the server. -class TableListView extends React.Component< - TableListViewProps, - TableListViewState -> { - private _isMounted = false; - - constructor(props: TableListViewProps) { - super(props); - - this.state = { - items: [], - totalItems: 0, - hasInitialFetchReturned: false, - hasUpdatedAtMetadata: null, - isFetchingItems: false, - isDeletingItems: false, - showDeleteModal: false, - showLimitError: false, - filter: props.initialFilter, - selectedIds: [], - pagination: { - pageIndex: 0, - totalItemCount: 0, - pageSize: props.initialPageSize, - pageSizeOptions: uniq([10, 20, 50, props.initialPageSize]).sort(), - }, - }; - } - - UNSAFE_componentWillMount() { - this._isMounted = true; - } - - componentWillUnmount() { - this._isMounted = false; - this.debouncedFetch.cancel(); - } - - componentDidMount() { - this.fetchItems(); - } - - componentDidUpdate(prevProps: TableListViewProps, prevState: TableListViewState) { - if (this.state.hasUpdatedAtMetadata === null && prevState.items !== this.state.items) { - // We check if the saved object have the "updatedAt" metadata - // to render or not that column in the table - const hasUpdatedAtMetadata = Boolean( - this.state.items.find((item: { updatedAt?: string }) => Boolean(item.updatedAt)) - ); - - this.setState((prev) => { - return { - hasUpdatedAtMetadata, - tableSort: hasUpdatedAtMetadata - ? { - field: 'updatedAt' as keyof V, - direction: 'desc' as const, - } - : prev.tableSort, - pagination: { - ...prev.pagination, - totalItemCount: this.state.items.length, - }, - }; - }); - } - } - - debouncedFetch = debounce(async (filter: string) => { - try { - const response = await this.props.findItems(filter); - - if (!this._isMounted) { - return; - } - - // We need this check to handle the case where search results come back in a different - // order than they were sent out. Only load results for the most recent search. - // Also, in case filter is empty, items are being pre-sorted alphabetically. - if (filter === this.state.filter) { - this.setState({ - hasInitialFetchReturned: true, - isFetchingItems: false, - items: !filter ? sortBy(response.hits, 'title') : response.hits, - totalItems: response.total, - showLimitError: response.total > this.props.listingLimit, - }); - } - } catch (fetchError) { - this.setState({ - hasInitialFetchReturned: true, - isFetchingItems: false, - items: [], - totalItems: 0, - showLimitError: false, - fetchError, - }); - } - }, 300); - - fetchItems = () => { - this.setState( - { - isFetchingItems: true, - fetchError: undefined, - }, - this.debouncedFetch.bind(null, this.state.filter) - ); - }; - - deleteSelectedItems = async () => { - if (this.state.isDeletingItems || !this.props.deleteItems) { - return; - } - this.setState({ - isDeletingItems: true, - }); - try { - const itemsById = keyBy(this.state.items, 'id'); - await this.props.deleteItems(this.state.selectedIds.map((id) => itemsById[id])); - } catch (error) { - this.props.toastNotifications.addDanger({ - title: toMountPoint( - , - { theme$: this.props.theme.theme$ } - ), - text: `${error}`, - }); - } - this.fetchItems(); - this.setState({ - isDeletingItems: false, - selectedIds: [], - }); - this.closeDeleteModal(); - }; - - closeDeleteModal = () => { - this.setState({ showDeleteModal: false }); - }; - - openDeleteModal = () => { - this.setState({ showDeleteModal: true }); - }; - - setFilter({ queryText }: { queryText: string }) { - // If the user is searching, we want to clear the sort order so that - // results are ordered by Elasticsearch's relevance. - this.setState( - { - filter: queryText, - }, - this.fetchItems - ); - } - - hasNoItems() { - if (!this.state.isFetchingItems && this.state.items.length === 0 && !this.state.filter) { - return true; - } - - return false; - } - - renderConfirmDeleteModal() { - let deleteButton = ( - - ); - if (this.state.isDeletingItems) { - deleteButton = ( - - ); - } - - return ( - - } - buttonColor="danger" - onCancel={this.closeDeleteModal} - onConfirm={this.deleteSelectedItems} - cancelButtonText={ - - } - confirmButtonText={deleteButton} - defaultFocusedButton="cancel" - > -

- -

-
- ); - } - - renderListingLimitWarning() { - if (this.state.showLimitError) { - const canEditAdvancedSettings = this.props.application.capabilities.advancedSettings.save; - const setting = 'savedObjects:listingLimit'; - const advancedSettingsLink = this.props.application.getUrlForApp('management', { - path: `/kibana/settings?query=${setting}`, - }); - return ( - - - } - color="warning" - iconType="help" - > -

- {canEditAdvancedSettings ? ( - listingLimit, - advancedSettingsLink: ( - - - - ), - }} - /> - ) : ( - listingLimit, - }} - /> - )} -

-
- -
- ); - } - } - - renderFetchError() { - if (this.state.fetchError) { - return ( - - - } - color="danger" - iconType="alert" - > -

- -

-
- -
- ); - } - } - - renderNoItemsMessage() { - if (this.props.emptyPrompt) { - return this.props.emptyPrompt; - } else { - return ( - - { - - } - - } - actions={this.renderCreateButton()} - /> - ); - } - } - - renderToolsLeft() { - const selection = this.state.selectedIds; - - if (selection.length === 0) { - return; - } - - const onClick = () => { - this.openDeleteModal(); - }; - - return ( - - - - ); - } - - onTableChange(criteria: CriteriaWithPagination) { - this.setState((prev) => { - const tableSort = criteria.sort ?? prev.tableSort; - return { - pagination: { - ...prev.pagination, - pageIndex: criteria.page.index, - pageSize: criteria.page.size, - }, - tableSort, - }; - }); - - if (criteria.sort) { - this.setState({ tableSort: criteria.sort }); - } - } - - renderTable() { - const { searchFilters } = this.props; - - const selection = this.props.deleteItems - ? { - onSelectionChange: (obj: V[]) => { - this.setState({ - selectedIds: obj - .map((item) => (item as Record)?.id) - .filter((id): id is string => Boolean(id)), - }); - }, - } - : undefined; - - const search = { - onChange: this.setFilter.bind(this), - toolsLeft: this.renderToolsLeft(), - defaultQuery: this.state.filter, - box: { - incremental: true, - 'data-test-subj': 'tableListSearchBox', - }, - filters: searchFilters ?? [], - }; - - const noItemsMessage = ( - - ); - - return ( - - ); - } - - getTableColumns() { - const columns = this.props.tableColumns.slice(); - - // Add "Last update" column - if (this.state.hasUpdatedAtMetadata) { - const renderUpdatedAt = (dateTime?: string) => { - if (!dateTime) { - return ( - - - - - ); - } - const updatedAt = moment(dateTime); - - if (updatedAt.diff(moment(), 'days') > -7) { - return ( - - {(formattedDate: string) => ( - - {formattedDate} - - )} - - ); - } - return ( - - {updatedAt.format('LL')} - - ); - }; - - columns.push({ - field: 'updatedAt', - name: i18n.translate('kibana-react.tableListView.lastUpdatedColumnTitle', { - defaultMessage: 'Last updated', - }), - render: (field: string, record: { updatedAt?: string }) => - renderUpdatedAt(record.updatedAt), - sortable: true, - width: '150px', - }); - } - - // Add "Actions" column - if (this.props.editItem) { - const actions: EuiTableActionsColumnType['actions'] = [ - { - name: (item) => - i18n.translate('kibana-react.tableListView.listing.table.editActionName', { - defaultMessage: 'Edit {itemDescription}', - values: { - itemDescription: get(item, this.props.rowHeader), - }, - }), - description: i18n.translate( - 'kibana-react.tableListView.listing.table.editActionDescription', - { - defaultMessage: 'Edit', - } - ), - icon: 'pencil', - type: 'icon', - enabled: (v) => !(v as unknown as { error: string })?.error, - onClick: this.props.editItem, - }, - ]; - - columns.push({ - name: i18n.translate('kibana-react.tableListView.listing.table.actionTitle', { - defaultMessage: 'Actions', - }), - width: '100px', - actions, - }); - } - - return columns; - } - - renderCreateButton() { - if (this.props.createItem) { - return ( - - - - ); - } - } - - render() { - const pageDTS = `${this.props.entityName}LandingPage`; - - if (!this.state.hasInitialFetchReturned) { - return <>; - } - - if (!this.state.fetchError && this.hasNoItems()) { - return ( - - {this.renderNoItemsMessage()} - - ); - } - - return ( - {this.props.tableListTitle}, - rightSideItems: [this.renderCreateButton()], - 'data-test-subj': 'top-nav', - }} - pageBodyProps={{ - 'aria-labelledby': this.state.hasInitialFetchReturned ? this.props.headingId : undefined, - }} - > - {this.state.showDeleteModal && this.renderConfirmDeleteModal()} - {this.props.children} - {this.renderListingLimitWarning()} - {this.renderFetchError()} - {this.renderTable()} - - ); - } -} - -export { TableListView }; - -// eslint-disable-next-line import/no-default-export -export default TableListView; diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index 6a5b99973dad3..4b393a7a1c249 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -152,6 +152,7 @@ export const applicationUsageSchema = { monitoring: commonSchema, 'observability-overview': commonSchema, osquery: commonSchema, + profiling: commonSchema, security_account: commonSchema, reportingRedirect: commonSchema, security_access_agreement: commonSchema, 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 cdae42f803a06..b20d0a7c3f3bd 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -518,6 +518,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'observability:apmLabsButton': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, 'observability:apmProgressiveLoading': { type: 'keyword', _meta: { description: 'Non-default value of setting.' }, @@ -554,4 +558,8 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'enterpriseSearch:enableBehavioralAnalyticsSection': { + 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 6dfb0432ff8bb..f6aaf1258c818 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -140,6 +140,7 @@ export interface UsageStats { 'lens:useFieldExistenceSampling': boolean; 'metrics:allowCheckingForFailedShards': boolean; 'observability:apmOperationsTab': boolean; + 'observability:apmLabsButton': boolean; 'observability:apmProgressiveLoading': string; 'observability:apmServiceGroupMaxNumberOfServices': number; 'observability:apmServiceInventoryOptimizedSorting': boolean; @@ -148,4 +149,5 @@ export interface UsageStats { 'securitySolution:showRelatedIntegrations': boolean; 'visualization:visualize:legacyGaugeChartsLibrary': boolean; 'enterpriseSearch:enableIndexTransformsTab': boolean; + 'enterpriseSearch:enableBehavioralAnalyticsSection': boolean; } diff --git a/src/plugins/saved_objects/public/types.ts b/src/plugins/saved_objects/public/types.ts index 1159a02dc0c33..ce3f41e5bb623 100644 --- a/src/plugins/saved_objects/public/types.ts +++ b/src/plugins/saved_objects/public/types.ts @@ -21,10 +21,10 @@ import type { DataView } from '@kbn/data-views-plugin/common'; * @deprecated * @removeBy 8.8.0 */ -export interface SavedObject { +export interface SavedObject { _serialize: () => { attributes: SavedObjectAttributes; references: SavedObjectReference[] }; _source: Record; - applyESResp: (resp: EsResponse) => Promise; + applyESResp: (resp: EsResponse) => Promise>; copyOnSave: boolean; creationOpts: (opts: SavedObjectCreationOpts) => Record; defaults: any; @@ -35,7 +35,7 @@ export interface SavedObject { getFullPath: () => string; hydrateIndexPattern?: (id?: string) => Promise; id?: string; - init?: () => Promise; + init?: () => Promise>; isSaving: boolean; isTitleChanged: () => boolean; lastSavedTitle: string; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 49eab35b3f154..a06857e3ace13 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -4527,6 +4527,137 @@ } } }, + "profiling": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "Always `main`" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 90 days" + } + }, + "views": { + "type": "array", + "items": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "The application view being tracked" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application sub view since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application sub view is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" + } + } + } + } + } + } + }, "security_account": { "properties": { "appId": { @@ -7605,125 +7736,6 @@ } } }, - "event_loop_delays": { - "properties": { - "daily": { - "type": "array", - "items": { - "properties": { - "processId": { - "type": "long", - "_meta": { - "description": "The process id of the monitored kibana instance." - } - }, - "instanceUuid": { - "type": "keyword", - "_meta": { - "description": "The uuid of the kibana instance." - } - }, - "fromTimestamp": { - "type": "date", - "_meta": { - "description": "Timestamp at which the histogram started monitoring." - } - }, - "lastUpdatedAt": { - "type": "date", - "_meta": { - "description": "Latest timestamp this histogram object was updated this day." - } - }, - "min": { - "type": "long", - "_meta": { - "description": "The minimum recorded event loop delay in ms." - } - }, - "max": { - "type": "long", - "_meta": { - "description": "The maximum recorded event loop delay in ms." - } - }, - "mean": { - "type": "long", - "_meta": { - "description": "The mean of the recorded event loop delays in ms." - } - }, - "exceeds": { - "type": "long", - "_meta": { - "description": "The number of times the event loop delay exceeded the maximum 1 hour eventloop delay threshold." - } - }, - "stddev": { - "type": "long", - "_meta": { - "description": "The standard deviation of the recorded event loop delays in ms." - } - }, - "percentiles": { - "properties": { - "50": { - "type": "long", - "_meta": { - "description": "The 50th accumulated percentile distribution in ms" - } - }, - "75": { - "type": "long", - "_meta": { - "description": "The 75th accumulated percentile distribution in ms" - } - }, - "95": { - "type": "long", - "_meta": { - "description": "The 95th accumulated percentile distribution in ms" - } - }, - "99": { - "type": "long", - "_meta": { - "description": "The 99th accumulated percentile distribution in ms" - } - } - } - } - } - } - } - } - }, - "localization": { - "properties": { - "locale": { - "type": "keyword", - "_meta": { - "description": "The default locale set on the Kibana system" - } - }, - "integrities": { - "properties": { - "DYNAMIC_KEY": { - "type": "text", - "_meta": { - "description": "Translation file hash. If the hash is different it indicates that a custom translation file is used" - } - } - } - }, - "labelsCount": { - "type": "long", - "_meta": { - "description": "The number of translated labels" - } - } - } - }, "stack_management": { "properties": { "securitySolution:defaultIndex": { @@ -8491,6 +8503,12 @@ "description": "Non-default value of setting." } }, + "observability:apmLabsButton": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, "observability:apmProgressiveLoading": { "type": "keyword", "_meta": { @@ -8544,6 +8562,131 @@ "_meta": { "description": "Non-default value of setting." } + }, + "enterpriseSearch:enableBehavioralAnalyticsSection": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + } + } + }, + "event_loop_delays": { + "properties": { + "daily": { + "type": "array", + "items": { + "properties": { + "processId": { + "type": "long", + "_meta": { + "description": "The process id of the monitored kibana instance." + } + }, + "instanceUuid": { + "type": "keyword", + "_meta": { + "description": "The uuid of the kibana instance." + } + }, + "fromTimestamp": { + "type": "date", + "_meta": { + "description": "Timestamp at which the histogram started monitoring." + } + }, + "lastUpdatedAt": { + "type": "date", + "_meta": { + "description": "Latest timestamp this histogram object was updated this day." + } + }, + "min": { + "type": "long", + "_meta": { + "description": "The minimum recorded event loop delay in ms." + } + }, + "max": { + "type": "long", + "_meta": { + "description": "The maximum recorded event loop delay in ms." + } + }, + "mean": { + "type": "long", + "_meta": { + "description": "The mean of the recorded event loop delays in ms." + } + }, + "exceeds": { + "type": "long", + "_meta": { + "description": "The number of times the event loop delay exceeded the maximum 1 hour eventloop delay threshold." + } + }, + "stddev": { + "type": "long", + "_meta": { + "description": "The standard deviation of the recorded event loop delays in ms." + } + }, + "percentiles": { + "properties": { + "50": { + "type": "long", + "_meta": { + "description": "The 50th accumulated percentile distribution in ms" + } + }, + "75": { + "type": "long", + "_meta": { + "description": "The 75th accumulated percentile distribution in ms" + } + }, + "95": { + "type": "long", + "_meta": { + "description": "The 95th accumulated percentile distribution in ms" + } + }, + "99": { + "type": "long", + "_meta": { + "description": "The 99th accumulated percentile distribution in ms" + } + } + } + } + } + } + } + } + }, + "localization": { + "properties": { + "locale": { + "type": "keyword", + "_meta": { + "description": "The default locale set on the Kibana system" + } + }, + "integrities": { + "properties": { + "DYNAMIC_KEY": { + "type": "text", + "_meta": { + "description": "Translation file hash. If the hash is different it indicates that a custom translation file is used" + } + } + } + }, + "labelsCount": { + "type": "long", + "_meta": { + "description": "The number of translated labels" + } } } }, @@ -10132,4 +10275,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx index d3c2c75bca853..048de833df802 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_listing.tsx @@ -18,7 +18,9 @@ import useMount from 'react-use/lib/useMount'; import { useLocation } from 'react-router-dom'; import { SavedObjectsFindOptionsReference } from '@kbn/core/public'; -import { useKibana, TableListView, useExecutionContext } from '@kbn/kibana-react-plugin/public'; +import { useKibana, useExecutionContext } from '@kbn/kibana-react-plugin/public'; +import { TableListView } from '@kbn/content-management-table-list'; +import type { UserContentCommonSchema } from '@kbn/content-management-table-list'; import { findListItems } from '../../utils/saved_visualize_utils'; import { showNewVisModal } from '../../wizard'; import { getTypes } from '../../services'; @@ -27,9 +29,47 @@ import { SAVED_OBJECTS_LIMIT_SETTING, SAVED_OBJECTS_PER_PAGE_SETTING, } from '../..'; +import type { VisualizationListItem } from '../..'; import { VisualizeServices } from '../types'; import { VisualizeConstants } from '../../../common/constants'; -import { getTableColumns, getNoItemsMessage } from '../utils'; +import { getNoItemsMessage, getCustomColumn } from '../utils'; +import { getVisualizeListItemLink } from '../utils/get_visualize_list_item_link'; +import { VisualizationStage } from '../../vis_types/vis_type_alias_registry'; + +interface VisualizeUserContent extends VisualizationListItem, UserContentCommonSchema { + type: string; + attributes: { + title: string; + description?: string; + editApp: string; + editUrl: string; + error?: string; + }; +} + +const toTableListViewSavedObject = (savedObject: Record): VisualizeUserContent => { + return { + id: savedObject.id as string, + updatedAt: savedObject.updatedAt as string, + references: savedObject.references as Array<{ id: string; type: string; name: string }>, + type: savedObject.savedObjectType as string, + editUrl: savedObject.editUrl as string, + editApp: savedObject.editApp as string, + icon: savedObject.icon as string, + stage: savedObject.stage as VisualizationStage, + savedObjectType: savedObject.savedObjectType as string, + typeTitle: savedObject.typeTitle as string, + title: (savedObject.title as string) ?? '', + error: (savedObject.error as string) ?? '', + attributes: { + title: (savedObject.title as string) ?? '', + description: savedObject.description as string, + editApp: savedObject.editApp as string, + editUrl: savedObject.editUrl as string, + error: savedObject.error as string, + }, + }; +}; export const VisualizeListing = () => { const { @@ -42,12 +82,10 @@ export const VisualizeListing = () => { toastNotifications, stateTransferService, savedObjects, - savedObjectsTagging, uiSettings, visualizeCapabilities, dashboardCapabilities, kbnUrlStateStorage, - theme, }, } = useKibana(); const { pathname } = useLocation(); @@ -96,7 +134,7 @@ export const VisualizeListing = () => { }, []); const editItem = useCallback( - ({ editUrl, editApp }) => { + ({ attributes: { editUrl, editApp } }: VisualizeUserContent) => { if (editApp) { application.navigateToApp(editApp, { path: editUrl }); return; @@ -108,22 +146,9 @@ export const VisualizeListing = () => { ); const noItemsFragment = useMemo(() => getNoItemsMessage(createNewVis), [createNewVis]); - const tableColumns = useMemo( - () => getTableColumns(core, kbnUrlStateStorage, savedObjectsTagging), - [core, kbnUrlStateStorage, savedObjectsTagging] - ); const fetchItems = useCallback( - (filter) => { - let searchTerm = filter; - let references: SavedObjectsFindOptionsReference[] | undefined; - - if (savedObjectsTagging) { - const parsedQuery = savedObjectsTagging.ui.parseSearchQuery(filter, { useName: true }); - searchTerm = parsedQuery.searchTerm; - references = parsedQuery.tagReferences; - } - + (searchTerm: string, references?: SavedObjectsFindOptionsReference[]) => { const isLabsEnabled = uiSettings.get(VISUALIZE_ENABLE_LABS_SETTING); return findListItems( savedObjects.client, @@ -133,10 +158,12 @@ export const VisualizeListing = () => { references ).then(({ total, hits }: { total: number; hits: Array> }) => ({ total, - hits: hits.filter((result: any) => isLabsEnabled || result.type?.stage !== 'experimental'), + hits: hits + .filter((result: any) => isLabsEnabled || result.type?.stage !== 'experimental') + .map(toTableListViewSavedObject), })); }, - [listingLimit, uiSettings, savedObjectsTagging, savedObjects.client] + [listingLimit, uiSettings, savedObjects.client] ); const deleteItems = useCallback( @@ -154,12 +181,6 @@ export const VisualizeListing = () => { [savedObjects.client, toastNotifications] ); - const searchFilters = useMemo(() => { - return savedObjectsTagging - ? [savedObjectsTagging.ui.getSearchBarFilter({ useName: true })] - : []; - }, [savedObjectsTagging]); - const calloutMessage = ( { ); return ( - + id="vis" headingId="visualizeListingHeading" // we allow users to create visualizations even if they can't save them // for data exploration purposes createItem={createNewVis} - tableCaption={i18n.translate('visualizations.listing.table.listTitle', { - defaultMessage: 'Visualize Library', - })} findItems={fetchItems} deleteItems={visualizeCapabilities.delete ? deleteItems : undefined} editItem={visualizeCapabilities.save ? editItem : undefined} - tableColumns={tableColumns} + customTableColumn={getCustomColumn()} listingLimit={listingLimit} initialPageSize={initialPageSize} initialFilter={''} - rowHeader="title" emptyPrompt={noItemsFragment} entityName={i18n.translate('visualizations.listing.table.entityName', { defaultMessage: 'visualization', @@ -211,10 +229,9 @@ export const VisualizeListing = () => { tableListTitle={i18n.translate('visualizations.listing.table.listTitle', { defaultMessage: 'Visualize Library', })} - toastNotifications={toastNotifications} - searchFilters={searchFilters} - theme={theme} - application={application} + getDetailViewLink={({ attributes: { editApp, editUrl, error } }) => + getVisualizeListItemLink(core.application, kbnUrlStateStorage, editApp, editUrl, error) + } > {dashboardCapabilities.createNew && ( <> diff --git a/src/plugins/visualizations/public/visualize_app/index.tsx b/src/plugins/visualizations/public/visualize_app/index.tsx index c29eef172ffe6..e432275c755e6 100644 --- a/src/plugins/visualizations/public/visualize_app/index.tsx +++ b/src/plugins/visualizations/public/visualize_app/index.tsx @@ -11,7 +11,13 @@ import ReactDOM from 'react-dom'; import { Router } from 'react-router-dom'; import { AppMountParameters } from '@kbn/core/public'; -import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { + KibanaContextProvider, + KibanaThemeProvider, + toMountPoint, +} from '@kbn/kibana-react-plugin/public'; +import { FormattedRelative } from '@kbn/i18n-react'; +import { TableListViewKibanaProvider } from '@kbn/content-management-table-list'; import { VisualizeApp } from './app'; import { VisualizeServices } from './types'; import { addHelpMenuToAppChrome, addBadgeToAppChrome } from './utils'; @@ -33,7 +39,16 @@ export const renderApp = ( - + + + diff --git a/src/plugins/visualizations/public/visualize_app/utils/get_table_columns.tsx b/src/plugins/visualizations/public/visualize_app/utils/get_table_columns.tsx index adb9e3d9ef3e9..8d907b763a035 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/get_table_columns.tsx +++ b/src/plugins/visualizations/public/visualize_app/utils/get_table_columns.tsx @@ -7,23 +7,10 @@ */ import React from 'react'; -import { - EuiBetaBadge, - EuiButton, - EuiEmptyPrompt, - EuiIcon, - EuiLink, - EuiBadge, - EuiBasicTableColumn, -} from '@elastic/eui'; +import { EuiBetaBadge, EuiButton, EuiEmptyPrompt, EuiIcon, EuiBadge } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; -import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; -import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; -import type { CoreStart } from '@kbn/core/public'; import { VisualizationListItem } from '../..'; -import { getVisualizeListItemLink } from './get_visualize_list_item_link'; const getBadge = (item: VisualizationListItem) => { if (item.stage === 'beta') { @@ -79,67 +66,27 @@ const renderItemTypeIcon = (item: VisualizationListItem) => { return icon; }; -export const getTableColumns = ( - core: CoreStart, - kbnUrlStateStorage: IKbnUrlStateStorage, - taggingApi?: SavedObjectsTaggingApi -) => - [ - { - field: 'title', - name: i18n.translate('visualizations.listing.table.titleColumnName', { - defaultMessage: 'Title', - }), - sortable: true, - render: (field: string, { editApp, editUrl, title, error }: VisualizationListItem) => - // In case an error occurs i.e. the vis has wrong type, we render the vis but without the link - !error ? ( - - - {field} - - - ) : ( - field - ), - }, - { - field: 'typeTitle', - name: i18n.translate('visualizations.listing.table.typeColumnName', { - defaultMessage: 'Type', - }), - sortable: true, - render: (field: string, record: VisualizationListItem) => - !record.error ? ( - - {renderItemTypeIcon(record)} - {record.typeTitle} - {getBadge(record)} - - ) : ( - - {record.error} - - ), - }, - { - field: 'description', - name: i18n.translate('visualizations.listing.table.descriptionColumnName', { - defaultMessage: 'Description', - }), - sortable: true, - render: (field: string, record: VisualizationListItem) => {record.description}, - }, - ...(taggingApi ? [taggingApi.ui.getTableColumnDefinition()] : []), - ] as unknown as Array>>; +export const getCustomColumn = () => { + return { + field: 'typeTitle', + name: i18n.translate('visualizations.listing.table.typeColumnName', { + defaultMessage: 'Type', + }), + sortable: true, + render: (field: string, record: VisualizationListItem) => + !record.error ? ( + + {renderItemTypeIcon(record)} + {record.typeTitle} + {getBadge(record)} + + ) : ( + + {record.error} + + ), + }; +}; export const getNoItemsMessage = (createItem: () => void) => ( { + if (error) { + return undefined; + } + // for visualizations the editApp is undefined let url = application.getUrlForApp(editApp ?? VISUALIZE_APP_NAME, { path: editApp ? editUrl : `#${editUrl}`, diff --git a/test/accessibility/services/a11y/a11y.ts b/test/accessibility/services/a11y/a11y.ts index e04e38cb9f72f..dd0d6c7f682e3 100644 --- a/test/accessibility/services/a11y/a11y.ts +++ b/test/accessibility/services/a11y/a11y.ts @@ -7,7 +7,7 @@ */ import chalk from 'chalk'; -import testSubjectToCss from '@kbn/test-subj-selector'; +import { subj as testSubjectToCss } from '@kbn/test-subj-selector'; import { AXE_CONFIG, AXE_OPTIONS } from '@kbn/axe-config'; import { FtrService } from '../../ftr_provider_context'; diff --git a/test/api_integration/apis/unified_field_list/field_stats.ts b/test/api_integration/apis/unified_field_list/field_stats.ts index 1a359c09f146b..5d41d412dbb06 100644 --- a/test/api_integration/apis/unified_field_list/field_stats.ts +++ b/test/api_integration/apis/unified_field_list/field_stats.ts @@ -411,7 +411,7 @@ export default ({ getService }: FtrProviderContext) => { expect(body.totalDocuments).to.eql(4634); expect(body.sampledDocuments).to.eql(100); expect(body.sampledValues).to.eql(100); - expect(body.topValues.buckets.length).to.eql(5); + expect(body.topValues.buckets.length).to.be.greaterThan(0); }); it('should return top values for index pattern runtime string fields', async () => { diff --git a/test/examples/expressions_explorer/expressions.ts b/test/examples/expressions_explorer/expressions.ts index 9d55458426776..c7b28cdd03c29 100644 --- a/test/examples/expressions_explorer/expressions.ts +++ b/test/examples/expressions_explorer/expressions.ts @@ -7,7 +7,7 @@ */ import expect from '@kbn/expect'; -import testSubjSelector from '@kbn/test-subj-selector'; +import { subj as testSubjSelector } from '@kbn/test-subj-selector'; import { PluginFunctionalProviderContext } from '../../plugin_functional/services'; // eslint-disable-next-line import/no-default-export diff --git a/test/functional/apps/console/_console.ts b/test/functional/apps/console/_console.ts index 57aab9b80bb66..65511e80e5457 100644 --- a/test/functional/apps/console/_console.ts +++ b/test/functional/apps/console/_console.ts @@ -144,7 +144,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('multiple requests output', () => { + describe('multiple requests output', function () { const sendMultipleRequests = async (requests: string[]) => { await asyncForEach(requests, async (request) => { await PageObjects.console.enterRequest(request); diff --git a/test/functional/apps/discover/group1/_discover.ts b/test/functional/apps/discover/group1/_discover.ts index 3b4e85089db4f..39793c9c047f0 100644 --- a/test/functional/apps/discover/group1/_discover.ts +++ b/test/functional/apps/discover/group1/_discover.ts @@ -19,6 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const queryBar = getService('queryBar'); const inspector = getService('inspector'); const elasticChart = getService('elasticChart'); + const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); const defaultSettings = { @@ -342,5 +343,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.pauseAutoRefresh(); }); }); + + describe('resizable layout panels', () => { + it('should allow resizing the layout panels', async () => { + const resizeDistance = 100; + const topPanel = await testSubjects.find('dscResizablePanelTop'); + const mainPanel = await testSubjects.find('dscResizablePanelMain'); + const resizeButton = await testSubjects.find('dsc-resizable-button'); + const topPanelSize = (await topPanel.getPosition()).height; + const mainPanelSize = (await mainPanel.getPosition()).height; + await browser.dragAndDrop( + { location: resizeButton }, + { location: { x: 0, y: resizeDistance } } + ); + const newTopPanelSize = (await topPanel.getPosition()).height; + const newMainPanelSize = (await mainPanel.getPosition()).height; + expect(newTopPanelSize).to.be(topPanelSize + resizeDistance); + expect(newMainPanelSize).to.be(mainPanelSize - resizeDistance); + }); + }); }); } diff --git a/test/functional/page_objects/console_page.ts b/test/functional/page_objects/console_page.ts index d4079012bd51e..f7b5a5b13c87d 100644 --- a/test/functional/page_objects/console_page.ts +++ b/test/functional/page_objects/console_page.ts @@ -211,21 +211,11 @@ export class ConsolePageObject extends FtrService { } public async hasSuccessBadge() { - try { - const responseEditor = await this.testSubjects.find('response-editor'); - return Boolean(await responseEditor.findByCssSelector('.ace_badge--success')); - } catch (e) { - return false; - } + return await this.find.existsByCssSelector('.ace_badge--success'); } public async hasWarningBadge() { - try { - const responseEditor = await this.testSubjects.find('response-editor'); - return Boolean(await responseEditor.findByCssSelector('.ace_badge--warning')); - } catch (e) { - return false; - } + return await this.find.existsByCssSelector('.ace_badge--warning'); } public async hasInvalidSyntax() { diff --git a/test/functional/services/common/test_subjects.ts b/test/functional/services/common/test_subjects.ts index 0e9d7ed9ce931..8b3697e9c0e5a 100644 --- a/test/functional/services/common/test_subjects.ts +++ b/test/functional/services/common/test_subjects.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import testSubjSelector from '@kbn/test-subj-selector'; +import { subj as testSubjSelector } from '@kbn/test-subj-selector'; import { WebElementWrapper } from '../lib/web_element_wrapper'; import { FtrService } from '../../ftr_provider_context'; import { TimeoutOpt } from './types'; diff --git a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts index a4f0d40b6db95..144b625121f7e 100644 --- a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts +++ b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts @@ -10,7 +10,7 @@ import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { WebElement, WebDriver, By, Key } from 'selenium-webdriver'; import { PNG } from 'pngjs'; import cheerio from 'cheerio'; -import testSubjSelector from '@kbn/test-subj-selector'; +import { subj as testSubjSelector } from '@kbn/test-subj-selector'; import { ToolingLog } from '@kbn/tooling-log'; import { CustomCheerio, CustomCheerioStatic } from './custom_cheerio_api'; // @ts-ignore not supported yet diff --git a/test/scripts/jenkins_storybook.sh b/test/scripts/jenkins_storybook.sh index a07a634d2dba2..1c6faa93d01d1 100755 --- a/test/scripts/jenkins_storybook.sh +++ b/test/scripts/jenkins_storybook.sh @@ -7,6 +7,7 @@ cd "$KIBANA_DIR" yarn storybook --site apm yarn storybook --site canvas yarn storybook --site ci_composite +yarn storybook --site content_management yarn storybook --site custom_integrations yarn storybook --site dashboard yarn storybook --site dashboard_enhanced diff --git a/tsconfig.base.json b/tsconfig.base.json index 431d898f3773d..9f04a4d6a1e4e 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -389,6 +389,8 @@ "@kbn/osquery-plugin/*": ["x-pack/plugins/osquery/*"], "@kbn/painless-lab-plugin": ["x-pack/plugins/painless_lab"], "@kbn/painless-lab-plugin/*": ["x-pack/plugins/painless_lab/*"], + "@kbn/profiling-plugin": ["x-pack/plugins/profiling"], + "@kbn/profiling-plugin/*": ["x-pack/plugins/profiling/*"], "@kbn/remote-clusters-plugin": ["x-pack/plugins/remote_clusters"], "@kbn/remote-clusters-plugin/*": ["x-pack/plugins/remote_clusters/*"], "@kbn/reporting-plugin": ["x-pack/plugins/reporting"], @@ -417,6 +419,8 @@ "@kbn/spaces-plugin/*": ["x-pack/plugins/spaces/*"], "@kbn/stack-alerts-plugin": ["x-pack/plugins/stack_alerts"], "@kbn/stack-alerts-plugin/*": ["x-pack/plugins/stack_alerts/*"], + "@kbn/stack-connectors-plugin": ["x-pack/plugins/stack_connectors"], + "@kbn/stack-connectors-plugin/*": ["x-pack/plugins/stack_connectors/*"], "@kbn/synthetics-plugin": ["x-pack/plugins/synthetics"], "@kbn/synthetics-plugin/*": ["x-pack/plugins/synthetics/*"], "@kbn/task-manager-plugin": ["x-pack/plugins/task_manager"], diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index c527e0a3d9556..8c1b4ce3daa86 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -5,6 +5,7 @@ "xpack.alerting": "plugins/alerting", "xpack.eventLog": "plugins/event_log", "xpack.stackAlerts": "plugins/stack_alerts", + "xpack.stackConnectors": "plugins/stack_connectors", "xpack.apm": "plugins/apm", "xpack.canvas": "plugins/canvas", "xpack.cases": "plugins/cases", @@ -45,6 +46,7 @@ "xpack.monitoring": ["plugins/monitoring"], "xpack.osquery": ["plugins/osquery"], "xpack.painlessLab": "plugins/painless_lab", + "xpack.profiling": [ "plugins/profiling" ], "xpack.remoteClusters": "plugins/remote_clusters", "xpack.reporting": ["plugins/reporting"], "xpack.rollupJobs": ["plugins/rollup"], diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md index 213ec6d8f23ce..7cab1ffe0c0b3 100644 --- a/x-pack/plugins/actions/README.md +++ b/x-pack/plugins/actions/README.md @@ -32,49 +32,7 @@ Table of Contents - [Example](#example-1) - [actionsClient.execute(options)](#actionsclientexecuteoptions) - [Example](#example-2) -- [Built-in Action Types](#built-in-action-types) - - [ServiceNow ITSM](#servicenow-itsm) - - [`params`](#params) - - [`subActionParams (pushToService)`](#subactionparams-pushtoservice) - - [`subActionParams (getFields)`](#subactionparams-getfields) - - [`subActionParams (getIncident)`](#subactionparams-getincident) - - [`subActionParams (getChoices)`](#subactionparams-getchoices) - - [ServiceNow Sec Ops](#servicenow-sec-ops) - - [`params`](#params-1) - - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-1) - - [`subActionParams (getFields)`](#subactionparams-getfields-1) - - [`subActionParams (getIncident)`](#subactionparams-getincident-1) - - [`subActionParams (getChoices)`](#subactionparams-getchoices-1) - - [ServiceNow ITOM](#servicenow-itom) - - [`params`](#params-2) - - [`subActionParams (addEvent)`](#subactionparams-addevent) - - [`subActionParams (getChoices)`](#subactionparams-getchoices-2) - - [Jira](#jira) - - [`params`](#params-3) - - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-2) - - [`subActionParams (getIncident)`](#subactionparams-getincident-2) - - [`subActionParams (issueTypes)`](#subactionparams-issuetypes) - - [`subActionParams (fieldsByIssueType)`](#subactionparams-fieldsbyissuetype) - - [`subActionParams (issues)`](#subactionparams-issues) - - [`subActionParams (issue)`](#subactionparams-issue) - - [`subActionParams (getFields)`](#subactionparams-getfields-2) - - [IBM Resilient](#ibm-resilient) - - [`params`](#params-4) - - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-3) - - [`subActionParams (getFields)`](#subactionparams-getfields-3) - - [`subActionParams (incidentTypes)`](#subactionparams-incidenttypes) - - [`subActionParams (severity)`](#subactionparams-severity) - - [Swimlane](#swimlane) - - [`params`](#params-5) - - [| severity | The severity of the incident. | string _(optional)_ |](#-severity-----the-severity-of-the-incident-----string-optional-) - [Command Line Utility](#command-line-utility) -- [Developing New Action Types](#developing-new-action-types) - - [licensing](#licensing) - - [plugin location](#plugin-location) - - [documentation](#documentation) - - [tests](#tests) - - [action type config and secrets](#action-type-config-and-secrets) - - [user interface](#user-interface) ## Terminology @@ -251,292 +209,6 @@ const result = await actionsClient.execute({ }), }); ``` - -# Built-in Action Types - -Kibana ships with a set of built-in action types. See [Actions and connector types Documentation](https://www.elastic.co/guide/en/kibana/master/action-types.html). - -In addition to the documented configurations, several built in action type offer additional `params` configurations. - -## ServiceNow ITSM - -Refer to the [Run connector API documentation](https://www.elastic.co/guide/en/kibana/master/execute-connector-api.html#execute-connector-api-request-body) -for the full list of properties. - -### `params` - -| Property | Description | Type | -| --------------- | -------------------------------------------------------------------------------------------------- | ------ | -| subAction | The subaction to perform. It can be `pushToService`, `getFields`, `getIncident`, and `getChoices`. | string | -| subActionParams | The parameters of the subaction. | object | - -#### `subActionParams (pushToService)` - -| Property | Description | Type | -| -------- | ------------------------------------------------------------------------------------------------------------- | --------------------- | -| incident | The ServiceNow incident. | object | -| comments | The comments of the case. A comment is of the form `{ commentId: string, version: string, comment: string }`. | object[] _(optional)_ | - -The following table describes the properties of the `incident` object. - -| Property | Description | Type | -| ------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------- | -| short_description | The title of the incident. | string | -| description | The description of the incident. | string _(optional)_ | -| externalId | The ID of the incident in ServiceNow. If present, the incident is updated. Otherwise, a new incident is created. | string _(optional)_ | -| severity | The severity in ServiceNow. | string _(optional)_ | -| urgency | The urgency in ServiceNow. | string _(optional)_ | -| impact | The impact in ServiceNow. | string _(optional)_ | -| category | The category in ServiceNow. | string _(optional)_ | -| subcategory | The subcategory in ServiceNow. | string _(optional)_ | -| correlation_id | The correlation id of the incident. | string _(optional)_ | -| correlation_display | The correlation display of the ServiceNow. | string _(optional)_ | - -#### `subActionParams (getFields)` - -No parameters for the `getFields` subaction. Provide an empty object `{}`. - -#### `subActionParams (getIncident)` - -| Property | Description | Type | -| ---------- | ------------------------------------- | ------ | -| externalId | The ID of the incident in ServiceNow. | string | - - -#### `subActionParams (getChoices)` - -| Property | Description | Type | -| -------- | -------------------------------------------------- | -------- | -| fields | An array of fields. Example: `[category, impact]`. | string[] | - ---- - -## ServiceNow Sec Ops - -Refer to the [Run connector API documentation](https://www.elastic.co/guide/en/kibana/master/execute-connector-api.html#execute-connector-api-request-body) -for the full list of properties. - -### `params` - -| Property | Description | Type | -| --------------- | -------------------------------------------------------------------------------------------------- | ------ | -| subAction | The subaction to perform. It can be `pushToService`, `getFields`, `getIncident`, and `getChoices`. | string | -| subActionParams | The parameters of the subaction. | object | - -#### `subActionParams (pushToService)` - -| Property | Description | Type | -| -------- | ------------------------------------------------------------------------------------------------------------- | --------------------- | -| incident | The ServiceNow security incident. | object | -| comments | The comments of the case. A comment is of the form `{ commentId: string, version: string, comment: string }`. | object[] _(optional)_ | - -The following table describes the properties of the `incident` object. - -| Property | Description | Type | -| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -| short_description | The title of the security incident. | string | -| description | The description of the security incident. | string _(optional)_ | -| externalId | The ID of the security incident in ServiceNow. If present, the security incident is updated. Otherwise, a new security incident is created. | string _(optional)_ | -| priority | The priority in ServiceNow. | string _(optional)_ | -| dest_ip | A list of destination IPs related to the security incident. The IPs will be added as observables to the security incident. | (string \| string[]) _(optional)_ | -| source_ip | A list of source IPs related to the security incident. The IPs will be added as observables to the security incident. | (string \| string[]) _(optional)_ | -| malware_hash | A list of malware hashes related to the security incident. The hashes will be added as observables to the security incident. | (string \| string[]) _(optional)_ | -| malware_url | A list of malware URLs related to the security incident. The URLs will be added as observables to the security incident. | (string \| string[]) _(optional)_ | -| category | The category in ServiceNow. | string _(optional)_ | -| subcategory | The subcategory in ServiceNow. | string _(optional)_ | -| correlation_id | The correlation id of the security incident. | string _(optional)_ | -| correlation_display | The correlation display of the security incident. | string _(optional)_ | - -#### `subActionParams (getFields)` - -No parameters for the `getFields` subaction. Provide an empty object `{}`. - -#### `subActionParams (getIncident)` - -| Property | Description | Type | -| ---------- | ---------------------------------------------- | ------ | -| externalId | The ID of the security incident in ServiceNow. | string | - - -#### `subActionParams (getChoices)` - -| Property | Description | Type | -| -------- | ---------------------------------------------------- | -------- | -| fields | An array of fields. Example: `[priority, category]`. | string[] | - ---- -## ServiceNow ITOM - -Refer to the [Run connector API documentation](https://www.elastic.co/guide/en/kibana/master/execute-connector-api.html#execute-connector-api-request-body) -for the full list of properties. - -### `params` - -| Property | Description | Type | -| --------------- | ----------------------------------------------------------------- | ------ | -| subAction | The subaction to perform. It can be `addEvent`, and `getChoices`. | string | -| subActionParams | The parameters of the subaction. | object | - -#### `subActionParams (addEvent)` - - -| Property | Description | Type | -| --------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------- | -| source | The name of the event source type. | string _(optional)_ | -| event_class | Specific instance of the source. | string _(optional)_ | -| resource | The name of the resource. | string _(optional)_ | -| node | The Host that the event was triggered for. | string _(optional)_ | -| metric_name | Name of the metric. | string _(optional)_ | -| type | The type of event. | string _(optional)_ | -| severity | The category in ServiceNow. | string _(optional)_ | -| description | The subcategory in ServiceNow. | string _(optional)_ | -| additional_info | Any additional information about the event. | string _(optional)_ | -| message_key | This value is used for de-duplication of events. All actions sharing this key will be associated with the same ServiceNow alert. | string _(optional)_ | -| time_of_event | The time of the event. | string _(optional)_ | - -Refer to [ServiceNow documentation](https://docs.servicenow.com/bundle/rome-it-operations-management/page/product/event-management/task/send-events-via-web-service.html) for more information about the properties. - -#### `subActionParams (getChoices)` - -| Property | Description | Type | -| -------- | ------------------------------------------ | -------- | -| fields | An array of fields. Example: `[severity]`. | string[] | - ---- -## Jira - -The [Jira user documentation `params`](https://www.elastic.co/guide/en/kibana/master/jira-action-type.html) lists configuration properties for the `pushToService` subaction. In addition, several other subaction types are available. -### `params` - -| Property | Description | Type | -| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------ | -| subAction | The subaction to perform. It can be `pushToService`, `getIncident`, `issueTypes`, `fieldsByIssueType`, `issues`, `issue`, and `getFields`. | string | -| subActionParams | The parameters of the subaction. | object | - -#### `subActionParams (pushToService)` - -| Property | Description | Type | -| -------- | ------------------------------------------------------------------------------------------------------------- | --------------------- | -| incident | The Jira incident. | object | -| comments | The comments of the case. A comment is of the form `{ commentId: string, version: string, comment: string }`. | object[] _(optional)_ | - -The following table describes the properties of the `incident` object. - -| Property | Description | Type | -| ----------- | ------------------------------------------------------------------------------------------------------- | --------------------- | -| summary | The title of the issue. | string | -| description | The description of the issue. | string _(optional)_ | -| externalId | The ID of the issue in Jira. If present, the incident is updated. Otherwise, a new incident is created. | string _(optional)_ | -| issueType | The ID of the issue type in Jira. | string _(optional)_ | -| priority | The name of the priority in Jira. Example: `Medium`. | string _(optional)_ | -| labels | An array of labels. Labels cannot contain spaces. | string[] _(optional)_ | -| parent | The ID or key of the parent issue. Only for `Sub-task` issue types. | string _(optional)_ | - -#### `subActionParams (getIncident)` - -| Property | Description | Type | -| ---------- | ---------------------------- | ------ | -| externalId | The ID of the issue in Jira. | string | - -#### `subActionParams (issueTypes)` - -No parameters for the `issueTypes` subaction. Provide an empty object `{}`. - -#### `subActionParams (fieldsByIssueType)` - -| Property | Description | Type | -| -------- | --------------------------------- | ------ | -| id | The ID of the issue type in Jira. | string | - -#### `subActionParams (issues)` - -| Property | Description | Type | -| -------- | ------------------------ | ------ | -| title | The title to search for. | string | - -#### `subActionParams (issue)` - -| Property | Description | Type | -| -------- | ---------------------------- | ------ | -| id | The ID of the issue in Jira. | string | - -#### `subActionParams (getFields)` - -No parameters for the `getFields` subaction. Provide an empty object `{}`. - ---- - -## IBM Resilient - -The [IBM Resilient user documentation `params`](https://www.elastic.co/guide/en/kibana/master/resilient-action-type.html) lists configuration properties for the `pushToService` subaction. In addition, several other subaction types are available. - -### `params` - -| Property | Description | Type | -| --------------- | ------------------------------------------------------------------------------------------------- | ------ | -| subAction | The subaction to perform. It can be `pushToService`, `getFields`, `incidentTypes`, and `severity. | string | -| subActionParams | The parameters of the subaction. | object | - -#### `subActionParams (pushToService)` - -| Property | Description | Type | -| -------- | ------------------------------------------------------------------------------------------------------------- | --------------------- | -| incident | The IBM Resilient incident. | object | -| comments | The comments of the case. A comment is of the form `{ commentId: string, version: string, comment: string }`. | object[] _(optional)_ | - -The following table describes the properties of the `incident` object. - -| Property | Description | Type | -| ------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------- | -| name | The title of the incident. | string _(optional)_ | -| description | The description of the incident. | string _(optional)_ | -| externalId | The ID of the incident in IBM Resilient. If present, the incident is updated. Otherwise, a new incident is created. | string _(optional)_ | -| incidentTypes | An array with the IDs of IBM Resilient incident types. | number[] _(optional)_ | -| severityCode | IBM Resilient ID of the severity code. | number _(optional)_ | - -#### `subActionParams (getFields)` - -No parameters for the `getFields` subaction. Provide an empty object `{}`. - -#### `subActionParams (incidentTypes)` - -No parameters for the `incidentTypes` subaction. Provide an empty object `{}`. - -#### `subActionParams (severity)` - -No parameters for the `severity` subaction. Provide an empty object `{}`. - ---- -## Swimlane - - -### `params` - -| Property | Description | Type | -| --------------- | ---------------------------------------------------- | ------ | -| subAction | The subaction to perform. It can be `pushToService`. | string | -| subActionParams | The parameters of the subaction. | object | - - -`subActionParams (pushToService)` - -| Property | Description | Type | -| -------- | ------------------------------------------------------------------------------------------------------------- | --------------------- | -| incident | The Swimlane incident. | object | -| comments | The comments of the case. A comment is of the form `{ commentId: string, version: string, comment: string }`. | object[] _(optional)_ | - - -The following table describes the properties of the `incident` object. - -| Property | Description | Type | -| ----------- | -------------------------------- | ------------------- | -| alertId | The alert id. | string _(optional)_ | -| caseId | The case id of the incident. | string _(optional)_ | -| caseName | The case name of the incident. | string _(optional)_ | -| description | The description of the incident. | string _(optional)_ | -| ruleName | The rule name. | string _(optional)_ | -| severity | The severity of the incident. | string _(optional)_ | ---- # Command Line Utility The [`kbn-action`](https://github.com/pmuellr/kbn-action) tool can be used to send HTTP requests to the Actions plugin. For instance, to create a Slack action from the `.slack` Action Type, use the following command: @@ -556,43 +228,3 @@ $ kbn-action create .slack "post to slack" '{"webhookUrl": "https://hooks.slack. "version": "WzMsMV0=" } ``` - -# Developing New Action Types - -When creating a new action type, your plugin will eventually call `server.plugins.actions.setup.registerType()` to register the type with the actions plugin, but there are some additional things to think about about and implement. - -Consider working with the alerting team on early structure /design feedback of new actions, especially as the APIs and infrastructure are still under development. - -Don't forget to ping @elastic/security-detections-response to see if the new connector should be enabled within their solution. - -## licensing - -Currently actions are licensed as "basic" if the action only interacts with the stack, eg the server log and es index actions. Other actions are at least "gold" level. - -## plugin location - -Currently actions that are licensed as "basic" **MUST** be implemented in the actions plugin, other actions can be implemented in any other plugin that pre-reqs the actions plugin. If the new action is generic across the stack, it probably belongs in the actions plugin, but if your action is very specific to a plugin/solution, it might be easiest to implement it in the plugin/solution. Keep in mind that if Kibana is run without the plugin being enabled, any actions defined in that plugin will not run, nor will those actions be available via APIs or UI. - -Actions that take URLs or hostnames should check that those values are allowed. The allowed host list utilities are currently internal to the actions plugin, and so such actions will need to be implemented in the actions plugin. Longer-term, we will expose these utilities so they can be used by alerts implemented in other plugins; see [issue #64659](https://github.com/elastic/kibana/issues/64659). - -## documentation - -You should create asciidoc for the new action type. Add an entry to the action type index - [`docs/user/alerting/action-types.asciidoc`](../../../docs/user/alerting/action-types.asciidoc), which points to a new document for the action type that should be in the directory [`docs/user/alerting/action-types`](../../../docs/user/alerting/action-types). - -We suggest following the template provided in `docs/action-type-template.asciidoc`. The [Email action type](https://www.elastic.co/guide/en/kibana/master/email-action-type.html) is an example of documentation created following the template. - -## tests - -The action type should have both jest tests and functional tests. For functional tests, if your action interacts with a 3rd party service via HTTP, you may be able to create a simulator for your service, to test with. See the existing functional test servers in the directory [`x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server`](../../test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server) - -## action type config and secrets - -Action types must define `config` and `secrets` which are used to create connectors. This data should be described with `@kbn/config-schema` object schemas, and you **MUST NOT** use `schema.maybe()` to define properties. - -This is due to the fact that the structures are persisted in saved objects, which performs partial updates on the persisted data. If a property value is already persisted, but an update either doesn't include the property, or sets it to `undefined`, the persisted value will not be changed. Beyond this being a semantic error in general, it also ends up invalidating the encryption used to save secrets, and will render the secrets will not be able to be unencrypted later. - -Instead of `schema.maybe()`, use `schema.nullable()`, which is the same as `schema.maybe()` except that when passed an `undefined` value, the object returned from the validation will be set to `null`. The resulting type will be `property-type | null`, whereas with `schema.maybe()` it would be `property-type | undefined`. - -## user interface - -To make this action usable in the Kibana UI, you will need to provide all the UI editing aspects of the action. The existing action type user interfaces are defined in [`x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types`](../triggers_actions_ui/public/application/components/builtin_action_types). For more information, see the [UI documentation](../triggers_actions_ui/README.md#create-and-register-new-action-type-ui). \ No newline at end of file diff --git a/x-pack/plugins/actions/common/index.ts b/x-pack/plugins/actions/common/index.ts index 547aad66e6776..95b3d9e6c7c07 100644 --- a/x-pack/plugins/actions/common/index.ts +++ b/x-pack/plugins/actions/common/index.ts @@ -13,16 +13,8 @@ export * from './alert_history_schema'; export * from './rewrite_request_case'; export * from './mustache_template'; export * from './validate_email_addresses'; -export * from './servicenow_config'; export * from './connector_feature_config'; export const BASE_ACTION_API_PATH = '/api/actions'; export const INTERNAL_BASE_ACTION_API_PATH = '/internal/actions'; export const ACTIONS_FEATURE_ID = 'actions'; - -// supported values for `service` in addition to nodemailer's list of well-known services -export enum AdditionalEmailServices { - ELASTIC_CLOUD = 'elastic_cloud', - EXCHANGE = 'exchange_server', - OTHER = 'other', -} diff --git a/x-pack/plugins/actions/common/types.ts b/x-pack/plugins/actions/common/types.ts index 7db57477f8f3c..052ebc108077a 100644 --- a/x-pack/plugins/actions/common/types.ts +++ b/x-pack/plugins/actions/common/types.ts @@ -7,6 +7,12 @@ import { LicenseType } from '@kbn/licensing-plugin/common/types'; +export { + AlertingConnectorFeatureId, + CasesConnectorFeatureId, + UptimeConnectorFeatureId, + SecurityConnectorFeatureId, +} from './connector_feature_config'; export interface ActionType { id: string; name: string; diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index 1c1a6f7167b8f..79810d6cd8572 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -36,13 +36,13 @@ import { } from './authorization/get_authorization_mode_by_source'; import { actionsAuthorizationMock } from './authorization/actions_authorization.mock'; import { trackLegacyRBACExemption } from './lib/track_legacy_rbac_exemption'; -import { ConnectorTokenClient } from './builtin_action_types/lib/connector_token_client'; +import { ConnectorTokenClient } from './lib/connector_token_client'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { Logger } from '@kbn/core/server'; -import { connectorTokenClientMock } from './builtin_action_types/lib/connector_token_client.mock'; +import { connectorTokenClientMock } from './lib/connector_token_client.mock'; import { inMemoryMetricsMock } from './monitoring/in_memory_metrics.mock'; -import { getOAuthJwtAccessToken } from './builtin_action_types/lib/get_oauth_jwt_access_token'; -import { getOAuthClientCredentialsAccessToken } from './builtin_action_types/lib/get_oauth_client_credentials_access_token'; +import { getOAuthJwtAccessToken } from './lib/get_oauth_jwt_access_token'; +import { getOAuthClientCredentialsAccessToken } from './lib/get_oauth_client_credentials_access_token'; import { OAuthParams } from './routes/get_oauth_access_token'; jest.mock('@kbn/core-saved-objects-utils-server', () => { @@ -74,10 +74,10 @@ jest.mock('./authorization/get_authorization_mode_by_source', () => { }; }); -jest.mock('./builtin_action_types/lib/get_oauth_jwt_access_token', () => ({ +jest.mock('./lib/get_oauth_jwt_access_token', () => ({ getOAuthJwtAccessToken: jest.fn(), })); -jest.mock('./builtin_action_types/lib/get_oauth_client_credentials_access_token', () => ({ +jest.mock('./lib/get_oauth_client_credentials_access_token', () => ({ getOAuthClientCredentialsAccessToken: jest.fn(), })); diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index 73d5b32d077a9..232d292008023 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -55,7 +55,7 @@ import { } from './authorization/get_authorization_mode_by_source'; import { connectorAuditEvent, ConnectorAuditAction } from './lib/audit_events'; import { trackLegacyRBACExemption } from './lib/track_legacy_rbac_exemption'; -import { isConnectorDeprecated } from './lib/is_conector_deprecated'; +import { isConnectorDeprecated } from './lib/is_connector_deprecated'; import { ActionsConfigurationUtilities } from './actions_config'; import { OAuthClientCredentialsParams, @@ -66,12 +66,12 @@ import { getOAuthJwtAccessToken, GetOAuthJwtConfig, GetOAuthJwtSecrets, -} from './builtin_action_types/lib/get_oauth_jwt_access_token'; +} from './lib/get_oauth_jwt_access_token'; import { getOAuthClientCredentialsAccessToken, GetOAuthClientCredentialsConfig, GetOAuthClientCredentialsSecrets, -} from './builtin_action_types/lib/get_oauth_client_credentials_access_token'; +} from './lib/get_oauth_client_credentials_access_token'; // We are assuming there won't be many actions. This is why we will load // all the actions in advance and assume the total count to not go over 10000. diff --git a/x-pack/plugins/actions/server/actions_config.ts b/x-pack/plugins/actions/server/actions_config.ts index 49f1d1fd5445e..611cf4a394ad7 100644 --- a/x-pack/plugins/actions/server/actions_config.ts +++ b/x-pack/plugins/actions/server/actions_config.ts @@ -15,7 +15,7 @@ import { ActionsConfig, AllowedHosts, EnabledActionTypes, CustomHostSettings } f import { getCanonicalCustomHostUrl } from './lib/custom_host_settings'; import { ActionTypeDisabledError } from './lib'; import { ProxySettings, ResponseSettings, SSLSettings } from './types'; -import { getSSLSettingsFromConfig } from './builtin_action_types/lib/get_node_ssl_options'; +import { getSSLSettingsFromConfig } from './lib/get_node_ssl_options'; import { ValidateEmailAddressesOptions, validateEmailAddresses, diff --git a/x-pack/plugins/actions/server/builtin_action_types/index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/index.test.ts deleted file mode 100644 index 2465e64179a7b..0000000000000 --- a/x-pack/plugins/actions/server/builtin_action_types/index.test.ts +++ /dev/null @@ -1,65 +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 { ActionExecutor, TaskRunnerFactory } from '../lib'; -import { ActionTypeRegistry } from '../action_type_registry'; -import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; -import { registerBuiltInActionTypes } from '.'; -import { Logger } from '@kbn/core/server'; -import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { actionsConfigMock } from '../actions_config.mock'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; -import { inMemoryMetricsMock } from '../monitoring/in_memory_metrics.mock'; - -const ACTION_TYPE_IDS = [ - '.index', - '.email', - '.pagerduty', - '.server-log', - '.slack', - '.swimlane', - '.teams', - '.webhook', - '.xmatters', -]; - -export function createActionTypeRegistry(): { - logger: jest.Mocked; - actionTypeRegistry: ActionTypeRegistry; -} { - const logger = loggingSystemMock.create().get() as jest.Mocked; - const inMemoryMetrics = inMemoryMetricsMock.create(); - const actionTypeRegistry = new ActionTypeRegistry({ - taskManager: taskManagerMock.createSetup(), - licensing: licensingMock.createSetup(), - taskRunnerFactory: new TaskRunnerFactory( - new ActionExecutor({ isESOCanEncrypt: true }), - inMemoryMetrics - ), - actionsConfigUtils: actionsConfigMock.create(), - licenseState: licenseStateMock.create(), - preconfiguredActions: [], - }); - registerBuiltInActionTypes({ - logger, - actionTypeRegistry, - actionsConfigUtils: actionsConfigMock.create(), - }); - return { logger, actionTypeRegistry }; -} - -beforeEach(() => { - jest.resetAllMocks(); -}); - -describe('action is registered', () => { - test('gets registered with builtin actions', () => { - const { actionTypeRegistry } = createActionTypeRegistry(); - ACTION_TYPE_IDS.forEach((id) => expect(actionTypeRegistry.has(id)).toEqual(true)); - }); -}); diff --git a/x-pack/plugins/actions/server/builtin_action_types/index.ts b/x-pack/plugins/actions/server/builtin_action_types/index.ts deleted file mode 100644 index 587fdd4f20d2d..0000000000000 --- a/x-pack/plugins/actions/server/builtin_action_types/index.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Logger } from '@kbn/core/server'; -import { ActionTypeRegistry } from '../action_type_registry'; -import { ActionsConfigurationUtilities } from '../actions_config'; - -import { getActionType as getEmailActionType } from './email'; -import { getActionType as getIndexActionType } from './es_index'; -import { getActionType as getPagerDutyActionType } from './pagerduty'; -import { getActionType as getSwimlaneActionType } from './swimlane'; -import { getActionType as getServerLogActionType } from './server_log'; -import { getActionType as getSlackActionType } from './slack'; -import { getActionType as getWebhookActionType } from './webhook'; -import { getActionType as getCasesWebhookActionType } from './cases_webhook'; -import { getActionType as getXmattersActionType } from './xmatters'; -import { - getServiceNowITSMActionType, - getServiceNowSIRActionType, - getServiceNowITOMActionType, -} from './servicenow'; -import { getActionType as getJiraActionType } from './jira'; -import { getActionType as getResilientActionType } from './resilient'; -import { getActionType as getTeamsActionType } from './teams'; -export type { ActionParamsType as EmailActionParams } from './email'; -export { ActionTypeId as EmailActionTypeId } from './email'; -export type { ActionParamsType as IndexActionParams } from './es_index'; -export { ActionTypeId as IndexActionTypeId } from './es_index'; -export type { ActionParamsType as PagerDutyActionParams } from './pagerduty'; -export { ActionTypeId as PagerDutyActionTypeId } from './pagerduty'; -export type { ActionParamsType as ServerLogActionParams } from './server_log'; -export { ActionTypeId as ServerLogActionTypeId } from './server_log'; -export type { ActionParamsType as SlackActionParams } from './slack'; -export { ActionTypeId as SlackActionTypeId } from './slack'; -export type { ActionParamsType as WebhookActionParams } from './webhook'; -export type { ActionParamsType as CasesWebhookActionParams } from './cases_webhook'; -export { ActionTypeId as CasesWebhookActionTypeId } from './cases_webhook'; -export { ActionTypeId as WebhookActionTypeId } from './webhook'; -export type { ActionParamsType as XmattersActionParams } from './xmatters'; -export { ActionTypeId as XmattersActionTypeId } from './xmatters'; -export type { ActionParamsType as ServiceNowActionParams } from './servicenow'; -export { - ServiceNowITSMActionTypeId, - ServiceNowSIRActionTypeId, - ServiceNowITOMActionTypeId, -} from './servicenow'; -export type { ActionParamsType as JiraActionParams } from './jira'; -export { ActionTypeId as JiraActionTypeId } from './jira'; -export type { ActionParamsType as ResilientActionParams } from './resilient'; -export { ActionTypeId as ResilientActionTypeId } from './resilient'; -export type { ActionParamsType as TeamsActionParams } from './teams'; -export { ActionTypeId as TeamsActionTypeId } from './teams'; - -export function registerBuiltInActionTypes({ - actionsConfigUtils: configurationUtilities, - actionTypeRegistry, - logger, - publicBaseUrl, -}: { - actionsConfigUtils: ActionsConfigurationUtilities; - actionTypeRegistry: ActionTypeRegistry; - logger: Logger; - publicBaseUrl?: string; -}) { - actionTypeRegistry.register( - getEmailActionType({ logger, configurationUtilities, publicBaseUrl }) - ); - actionTypeRegistry.register(getIndexActionType({ logger })); - actionTypeRegistry.register(getPagerDutyActionType({ logger, configurationUtilities })); - actionTypeRegistry.register(getSwimlaneActionType({ logger, configurationUtilities })); - actionTypeRegistry.register(getServerLogActionType({ logger })); - actionTypeRegistry.register(getSlackActionType({ logger, configurationUtilities })); - actionTypeRegistry.register(getWebhookActionType({ logger, configurationUtilities })); - actionTypeRegistry.register(getCasesWebhookActionType({ logger, configurationUtilities })); - actionTypeRegistry.register(getXmattersActionType({ logger, configurationUtilities })); - actionTypeRegistry.register(getServiceNowITSMActionType({ logger, configurationUtilities })); - actionTypeRegistry.register(getServiceNowSIRActionType({ logger, configurationUtilities })); - actionTypeRegistry.register(getServiceNowITOMActionType({ logger, configurationUtilities })); - actionTypeRegistry.register(getJiraActionType({ logger, configurationUtilities })); - actionTypeRegistry.register(getResilientActionType({ logger, configurationUtilities })); - actionTypeRegistry.register(getTeamsActionType({ logger, configurationUtilities })); -} diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index d04d0209cba45..7f9b45c368e90 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -25,31 +25,6 @@ export type { FindActionResult, } from './types'; -export type { - CasesWebhookActionTypeId, - CasesWebhookActionParams, - EmailActionTypeId, - EmailActionParams, - IndexActionTypeId, - IndexActionParams, - PagerDutyActionTypeId, - PagerDutyActionParams, - ServerLogActionTypeId, - ServerLogActionParams, - SlackActionTypeId, - SlackActionParams, - WebhookActionTypeId, - WebhookActionParams, - ServiceNowITSMActionTypeId, - ServiceNowSIRActionTypeId, - ServiceNowActionParams, - JiraActionTypeId, - JiraActionParams, - ResilientActionTypeId, - ResilientActionParams, - TeamsActionTypeId, - TeamsActionParams, -} from './builtin_action_types'; export type { PluginSetupContract, PluginStartContract } from './plugin'; export { asSavedObjectExecutionSource, asHttpRequestExecutionSource } from './lib'; diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index dd4f15894ee28..00a3980833ef2 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -227,6 +227,7 @@ export class ActionExecutor { secrets: validatedSecrets, isEphemeral, taskInfo, + configurationUtilities, }); } catch (err) { if (err.reason === ActionExecutionErrorReason.Validation) { diff --git a/x-pack/plugins/actions/server/lib/axios_utils.test.ts b/x-pack/plugins/actions/server/lib/axios_utils.test.ts index 6de88c268c234..1f94e610f60bf 100644 --- a/x-pack/plugins/actions/server/lib/axios_utils.test.ts +++ b/x-pack/plugins/actions/server/lib/axios_utils.test.ts @@ -20,7 +20,7 @@ import { } from './axios_utils'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { actionsConfigMock } from '../actions_config.mock'; -import { getCustomAgents } from '../builtin_action_types/lib/get_custom_agents'; +import { getCustomAgents } from './get_custom_agents'; const TestUrl = 'https://elastic.co/foo/bar/baz'; diff --git a/x-pack/plugins/actions/server/lib/axios_utils.ts b/x-pack/plugins/actions/server/lib/axios_utils.ts index 18faa1f4d089c..9d1401ee65343 100644 --- a/x-pack/plugins/actions/server/lib/axios_utils.ts +++ b/x-pack/plugins/actions/server/lib/axios_utils.ts @@ -8,7 +8,7 @@ import { isObjectLike, isEmpty } from 'lodash'; import { AxiosInstance, Method, AxiosResponse, AxiosRequestConfig } from 'axios'; import { Logger } from '@kbn/core/server'; -import { getCustomAgents } from '../builtin_action_types/lib/get_custom_agents'; +import { getCustomAgents } from './get_custom_agents'; import { ActionsConfigurationUtilities } from '../actions_config'; export const request = async ({ diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/connector_token_client.mock.ts b/x-pack/plugins/actions/server/lib/connector_token_client.mock.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/lib/connector_token_client.mock.ts rename to x-pack/plugins/actions/server/lib/connector_token_client.mock.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/connector_token_client.test.ts b/x-pack/plugins/actions/server/lib/connector_token_client.test.ts similarity index 99% rename from x-pack/plugins/actions/server/builtin_action_types/lib/connector_token_client.test.ts rename to x-pack/plugins/actions/server/lib/connector_token_client.test.ts index 1aa8bbb3da9a7..d1308537400b4 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/connector_token_client.test.ts +++ b/x-pack/plugins/actions/server/lib/connector_token_client.test.ts @@ -10,7 +10,7 @@ import { loggingSystemMock, savedObjectsClientMock } from '@kbn/core/server/mock import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { ConnectorTokenClient } from './connector_token_client'; import { Logger } from '@kbn/core/server'; -import { ConnectorToken } from '../../types'; +import { ConnectorToken } from '../types'; const logger = loggingSystemMock.create().get() as jest.Mocked; jest.mock('@kbn/core-saved-objects-utils-server', () => { diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/connector_token_client.ts b/x-pack/plugins/actions/server/lib/connector_token_client.ts similarity index 98% rename from x-pack/plugins/actions/server/builtin_action_types/lib/connector_token_client.ts rename to x-pack/plugins/actions/server/lib/connector_token_client.ts index df1615d503329..79febc6f79dc8 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/connector_token_client.ts +++ b/x-pack/plugins/actions/server/lib/connector_token_client.ts @@ -8,8 +8,8 @@ import { omitBy, isUndefined } from 'lodash'; import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; import { Logger, SavedObjectsClientContract, SavedObjectsUtils } from '@kbn/core/server'; -import { ConnectorToken } from '../../types'; -import { CONNECTOR_TOKEN_SAVED_OBJECT_TYPE } from '../../constants/saved_objects'; +import { ConnectorToken } from '../types'; +import { CONNECTOR_TOKEN_SAVED_OBJECT_TYPE } from '../constants/saved_objects'; export const MAX_TOKENS_RETURNED = 1; diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/create_jwt_assertion.test.ts b/x-pack/plugins/actions/server/lib/create_jwt_assertion.test.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/lib/create_jwt_assertion.test.ts rename to x-pack/plugins/actions/server/lib/create_jwt_assertion.test.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/create_jwt_assertion.ts b/x-pack/plugins/actions/server/lib/create_jwt_assertion.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/lib/create_jwt_assertion.ts rename to x-pack/plugins/actions/server/lib/create_jwt_assertion.ts diff --git a/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts b/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts index 2bd3a6aa24576..e7d781a7218bd 100644 --- a/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts +++ b/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts @@ -7,11 +7,11 @@ import { LICENSE_TYPE } from '@kbn/licensing-plugin/common/types'; import { ActionType } from '../types'; -import { ServerLogActionTypeId, IndexActionTypeId } from '../builtin_action_types'; import { ActionTypeConfig, ActionTypeSecrets, ActionTypeParams } from '../types'; const CASE_ACTION_TYPE_ID = '.case'; - +const ServerLogActionTypeId = '.server-log'; +const IndexActionTypeId = '.index'; const ACTIONS_SCOPED_WITHIN_STACK = new Set([ ServerLogActionTypeId, IndexActionTypeId, diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.test.ts b/x-pack/plugins/actions/server/lib/get_custom_agents.test.ts similarity index 99% rename from x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.test.ts rename to x-pack/plugins/actions/server/lib/get_custom_agents.test.ts index 4d83cce7be1a7..9bf1e26f1d374 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.test.ts +++ b/x-pack/plugins/actions/server/lib/get_custom_agents.test.ts @@ -11,7 +11,7 @@ import { HttpsProxyAgent } from 'https-proxy-agent'; import { Logger } from '@kbn/core/server'; import { getCustomAgents } from './get_custom_agents'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { actionsConfigMock } from '../../actions_config.mock'; +import { actionsConfigMock } from '../actions_config.mock'; const logger = loggingSystemMock.create().get() as jest.Mocked; const targetHost = 'elastic.co'; diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.ts b/x-pack/plugins/actions/server/lib/get_custom_agents.ts similarity index 98% rename from x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.ts rename to x-pack/plugins/actions/server/lib/get_custom_agents.ts index 2654198828eef..49eca54afcde7 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.ts +++ b/x-pack/plugins/actions/server/lib/get_custom_agents.ts @@ -10,7 +10,7 @@ import { Agent as HttpsAgent, AgentOptions } from 'https'; import HttpProxyAgent from 'http-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { Logger } from '@kbn/core/server'; -import { ActionsConfigurationUtilities } from '../../actions_config'; +import { ActionsConfigurationUtilities } from '../actions_config'; import { getNodeSSLOptions, getSSLSettingsFromConfig } from './get_node_ssl_options'; interface GetCustomAgentsResponse { diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_node_ssl_options.test.ts b/x-pack/plugins/actions/server/lib/get_node_ssl_options.test.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/lib/get_node_ssl_options.test.ts rename to x-pack/plugins/actions/server/lib/get_node_ssl_options.test.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_node_ssl_options.ts b/x-pack/plugins/actions/server/lib/get_node_ssl_options.ts similarity index 97% rename from x-pack/plugins/actions/server/builtin_action_types/lib/get_node_ssl_options.ts rename to x-pack/plugins/actions/server/lib/get_node_ssl_options.ts index 5892442a1a319..e7246aaf3aa16 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_node_ssl_options.ts +++ b/x-pack/plugins/actions/server/lib/get_node_ssl_options.ts @@ -7,7 +7,7 @@ import { PeerCertificate } from 'tls'; import { Logger } from '@kbn/core/server'; -import { SSLSettings } from '../../types'; +import { SSLSettings } from '../types'; export function getNodeSSLOptions( logger: Logger, diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_oauth_client_credentials_access_token.test.ts b/x-pack/plugins/actions/server/lib/get_oauth_client_credentials_access_token.test.ts similarity index 99% rename from x-pack/plugins/actions/server/builtin_action_types/lib/get_oauth_client_credentials_access_token.test.ts rename to x-pack/plugins/actions/server/lib/get_oauth_client_credentials_access_token.test.ts index c3464a11e557e..8e611b0e5e67b 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_oauth_client_credentials_access_token.test.ts +++ b/x-pack/plugins/actions/server/lib/get_oauth_client_credentials_access_token.test.ts @@ -8,7 +8,7 @@ import sinon from 'sinon'; import { Logger } from '@kbn/core/server'; import { asyncForEach } from '@kbn/std'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { actionsConfigMock } from '../../actions_config.mock'; +import { actionsConfigMock } from '../actions_config.mock'; import { connectorTokenClientMock } from './connector_token_client.mock'; import { getOAuthClientCredentialsAccessToken } from './get_oauth_client_credentials_access_token'; import { requestOAuthClientCredentialsToken } from './request_oauth_client_credentials_token'; diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_oauth_client_credentials_access_token.ts b/x-pack/plugins/actions/server/lib/get_oauth_client_credentials_access_token.ts similarity index 97% rename from x-pack/plugins/actions/server/builtin_action_types/lib/get_oauth_client_credentials_access_token.ts rename to x-pack/plugins/actions/server/lib/get_oauth_client_credentials_access_token.ts index 1cce245a154c2..c6de6011d6f6d 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_oauth_client_credentials_access_token.ts +++ b/x-pack/plugins/actions/server/lib/get_oauth_client_credentials_access_token.ts @@ -5,8 +5,8 @@ * 2.0. */ import { Logger } from '@kbn/core/server'; -import { ActionsConfigurationUtilities } from '../../actions_config'; -import { ConnectorToken, ConnectorTokenClientContract } from '../../types'; +import { ActionsConfigurationUtilities } from '../actions_config'; +import { ConnectorToken, ConnectorTokenClientContract } from '../types'; import { requestOAuthClientCredentialsToken } from './request_oauth_client_credentials_token'; export interface GetOAuthClientCredentialsConfig { diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_oauth_jwt_access_token.test.ts b/x-pack/plugins/actions/server/lib/get_oauth_jwt_access_token.test.ts similarity index 99% rename from x-pack/plugins/actions/server/builtin_action_types/lib/get_oauth_jwt_access_token.test.ts rename to x-pack/plugins/actions/server/lib/get_oauth_jwt_access_token.test.ts index 0fe837fc0581a..f647056649a21 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_oauth_jwt_access_token.test.ts +++ b/x-pack/plugins/actions/server/lib/get_oauth_jwt_access_token.test.ts @@ -8,7 +8,7 @@ import sinon from 'sinon'; import { Logger } from '@kbn/core/server'; import { asyncForEach } from '@kbn/std'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { actionsConfigMock } from '../../actions_config.mock'; +import { actionsConfigMock } from '../actions_config.mock'; import { connectorTokenClientMock } from './connector_token_client.mock'; import { getOAuthJwtAccessToken } from './get_oauth_jwt_access_token'; import { createJWTAssertion } from './create_jwt_assertion'; diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_oauth_jwt_access_token.ts b/x-pack/plugins/actions/server/lib/get_oauth_jwt_access_token.ts similarity index 97% rename from x-pack/plugins/actions/server/builtin_action_types/lib/get_oauth_jwt_access_token.ts rename to x-pack/plugins/actions/server/lib/get_oauth_jwt_access_token.ts index 1233a61c0f3c8..ce12f035400b2 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_oauth_jwt_access_token.ts +++ b/x-pack/plugins/actions/server/lib/get_oauth_jwt_access_token.ts @@ -5,8 +5,8 @@ * 2.0. */ import { Logger } from '@kbn/core/server'; -import { ActionsConfigurationUtilities } from '../../actions_config'; -import { ConnectorToken, ConnectorTokenClientContract } from '../../types'; +import { ActionsConfigurationUtilities } from '../actions_config'; +import { ConnectorToken, ConnectorTokenClientContract } from '../types'; import { createJWTAssertion } from './create_jwt_assertion'; import { requestOAuthJWTToken } from './request_oauth_jwt_token'; diff --git a/x-pack/plugins/actions/server/lib/is_conector_deprecated.test.ts b/x-pack/plugins/actions/server/lib/is_connector_deprecated.test.ts similarity index 97% rename from x-pack/plugins/actions/server/lib/is_conector_deprecated.test.ts rename to x-pack/plugins/actions/server/lib/is_connector_deprecated.test.ts index c3697cea6a34e..8d2a698563b0c 100644 --- a/x-pack/plugins/actions/server/lib/is_conector_deprecated.test.ts +++ b/x-pack/plugins/actions/server/lib/is_connector_deprecated.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isConnectorDeprecated } from './is_conector_deprecated'; +import { isConnectorDeprecated } from './is_connector_deprecated'; describe('isConnectorDeprecated', () => { const connector = { diff --git a/x-pack/plugins/actions/server/lib/is_conector_deprecated.ts b/x-pack/plugins/actions/server/lib/is_connector_deprecated.ts similarity index 92% rename from x-pack/plugins/actions/server/lib/is_conector_deprecated.ts rename to x-pack/plugins/actions/server/lib/is_connector_deprecated.ts index ed46f5e685459..db5138cddc53f 100644 --- a/x-pack/plugins/actions/server/lib/is_conector_deprecated.ts +++ b/x-pack/plugins/actions/server/lib/is_connector_deprecated.ts @@ -40,7 +40,7 @@ export const isConnectorDeprecated = ( * the usesTableApi property to true to all connectors prior 7.16. Pre configured connectors * cannot be migrated. This check ensures that pre configured connectors without the * usesTableApi property explicitly in the kibana.yml file are considered deprecated. - * According to the schema defined here x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts + * According to the schema defined here x-pack/plugins/stack_connector/server/connector_types/servicenow/schema.ts * if the property is not defined it will be set to true at the execution of the connector. */ if (!Object.hasOwn(connector.config, 'usesTableApi')) { @@ -51,7 +51,7 @@ export const isConnectorDeprecated = ( * Connector created prior to 7.16 will be migrated to have the usesTableApi property set to true. * Connectors created after 7.16 should have the usesTableApi property set to true or false. * If the usesTableApi is omitted on an API call it will be defaulted to true. Check the schema - * here x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts. + * here x-pack/plugins/stack_connector/server/connector_types/servicenow/schema.ts. * The !! is to make TS happy. */ return !!connector.config.usesTableApi; diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_client_credentials_token.test.ts b/x-pack/plugins/actions/server/lib/request_oauth_client_credentials_token.test.ts similarity index 98% rename from x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_client_credentials_token.test.ts rename to x-pack/plugins/actions/server/lib/request_oauth_client_credentials_token.test.ts index 95964214dd6b1..20896e2691f10 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_client_credentials_token.test.ts +++ b/x-pack/plugins/actions/server/lib/request_oauth_client_credentials_token.test.ts @@ -11,7 +11,7 @@ jest.mock('axios', () => ({ import axios from 'axios'; import { Logger } from '@kbn/core/server'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { actionsConfigMock } from '../../actions_config.mock'; +import { actionsConfigMock } from '../actions_config.mock'; import { requestOAuthClientCredentialsToken } from './request_oauth_client_credentials_token'; const createAxiosInstanceMock = axios.create as jest.Mock; diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_client_credentials_token.ts b/x-pack/plugins/actions/server/lib/request_oauth_client_credentials_token.ts similarity index 90% rename from x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_client_credentials_token.ts rename to x-pack/plugins/actions/server/lib/request_oauth_client_credentials_token.ts index 784e5958803a2..9cfa2ead2f959 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_client_credentials_token.ts +++ b/x-pack/plugins/actions/server/lib/request_oauth_client_credentials_token.ts @@ -5,9 +5,9 @@ * 2.0. */ import { Logger } from '@kbn/core/server'; -import { ActionsConfigurationUtilities } from '../../actions_config'; +import { ActionsConfigurationUtilities } from '../actions_config'; import { OAuthTokenResponse, requestOAuthToken } from './request_oauth_token'; -import { RewriteResponseCase } from '../../../common'; +import { RewriteResponseCase } from '../../common'; export const OAUTH_CLIENT_CREDENTIALS_GRANT_TYPE = 'client_credentials'; diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_jwt_token.test.ts b/x-pack/plugins/actions/server/lib/request_oauth_jwt_token.test.ts similarity index 98% rename from x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_jwt_token.test.ts rename to x-pack/plugins/actions/server/lib/request_oauth_jwt_token.test.ts index 43d4fd7180549..318775762bbbd 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_jwt_token.test.ts +++ b/x-pack/plugins/actions/server/lib/request_oauth_jwt_token.test.ts @@ -11,7 +11,7 @@ jest.mock('axios', () => ({ import axios from 'axios'; import { Logger } from '@kbn/core/server'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { actionsConfigMock } from '../../actions_config.mock'; +import { actionsConfigMock } from '../actions_config.mock'; import { requestOAuthJWTToken } from './request_oauth_jwt_token'; const createAxiosInstanceMock = axios.create as jest.Mock; diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_jwt_token.ts b/x-pack/plugins/actions/server/lib/request_oauth_jwt_token.ts similarity index 91% rename from x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_jwt_token.ts rename to x-pack/plugins/actions/server/lib/request_oauth_jwt_token.ts index 84b31f590413f..d5d476c533557 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_jwt_token.ts +++ b/x-pack/plugins/actions/server/lib/request_oauth_jwt_token.ts @@ -5,9 +5,9 @@ * 2.0. */ import { Logger } from '@kbn/core/server'; -import { ActionsConfigurationUtilities } from '../../actions_config'; +import { ActionsConfigurationUtilities } from '../actions_config'; import { OAuthTokenResponse, requestOAuthToken } from './request_oauth_token'; -import { RewriteResponseCase } from '../../../common'; +import { RewriteResponseCase } from '../../common'; // This is a standard for JSON Web Token (JWT) Profile // for OAuth 2.0 Client Authentication and Authorization Grants https://datatracker.ietf.org/doc/html/rfc7523#section-8.1 diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_token.test.ts b/x-pack/plugins/actions/server/lib/request_oauth_token.test.ts similarity index 98% rename from x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_token.test.ts rename to x-pack/plugins/actions/server/lib/request_oauth_token.test.ts index 443a9234d7a08..cc9ea6a74517a 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_token.test.ts +++ b/x-pack/plugins/actions/server/lib/request_oauth_token.test.ts @@ -12,7 +12,7 @@ import axios from 'axios'; import { Logger } from '@kbn/core/server'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { requestOAuthToken } from './request_oauth_token'; -import { actionsConfigMock } from '../../actions_config.mock'; +import { actionsConfigMock } from '../actions_config.mock'; const createAxiosInstanceMock = axios.create as jest.Mock; const axiosInstanceMock = jest.fn(); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_token.ts b/x-pack/plugins/actions/server/lib/request_oauth_token.ts similarity index 90% rename from x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_token.ts rename to x-pack/plugins/actions/server/lib/request_oauth_token.ts index 20016989e63e7..116d3f2cc3315 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/request_oauth_token.ts +++ b/x-pack/plugins/actions/server/lib/request_oauth_token.ts @@ -9,9 +9,9 @@ import qs from 'query-string'; import axios from 'axios'; import stringify from 'json-stable-stringify'; import { Logger } from '@kbn/core/server'; -import { request } from '../../lib/axios_utils'; -import { ActionsConfigurationUtilities } from '../../actions_config'; -import { AsApiContract } from '../../../common'; +import { request } from './axios_utils'; +import { ActionsConfigurationUtilities } from '../actions_config'; +import { AsApiContract } from '../../common'; export interface OAuthTokenResponse { tokenType: string; diff --git a/x-pack/plugins/actions/server/mocks.ts b/x-pack/plugins/actions/server/mocks.ts index ebd7ed6bad453..3b8155818452f 100644 --- a/x-pack/plugins/actions/server/mocks.ts +++ b/x-pack/plugins/actions/server/mocks.ts @@ -16,7 +16,7 @@ import { actionsClientMock } from './actions_client.mock'; import { PluginSetupContract, PluginStartContract, renderActionParameterTemplates } from './plugin'; import { Services } from './types'; import { actionsAuthorizationMock } from './authorization/actions_authorization.mock'; -import { ConnectorTokenClient } from './builtin_action_types/lib/connector_token_client'; +import { ConnectorTokenClient } from './lib/connector_token_client'; export { actionsAuthorizationMock }; export { actionsClientMock }; const logger = loggingSystemMock.create().get() as jest.Mocked; diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index 250751a1f5a46..d560beab681c4 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -15,7 +15,7 @@ import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { eventLogMock } from '@kbn/event-log-plugin/server/mocks'; -import { ActionType, ActionsApiRequestHandlerContext } from './types'; +import { ActionType, ActionsApiRequestHandlerContext, ExecutorType } from './types'; import { ActionsConfig } from './config'; import { ActionsPlugin, @@ -25,6 +25,10 @@ import { } from './plugin'; import { AlertHistoryEsIndexConnectorId } from '../common'; +const executor: ExecutorType<{}, {}, {}, void> = async (options) => { + return { status: 'ok', actionId: options.actionId }; +}; + describe('Actions Plugin', () => { describe('setup()', () => { let context: PluginInitializerContext; @@ -383,7 +387,15 @@ describe('Actions Plugin', () => { setup(getConfig()); // coreMock.createSetup doesn't support Plugin generics // eslint-disable-next-line @typescript-eslint/no-explicit-any - await plugin.setup(coreSetup as any, pluginsSetup); + const pluginSetup = await plugin.setup(coreSetup as any, pluginsSetup); + pluginSetup.registerType({ + id: '.server-log', + name: 'Server log', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + executor, + }); + const pluginStart = await plugin.start(coreStart, pluginsStart); expect(pluginStart.preconfiguredActions.length).toEqual(1); @@ -393,7 +405,16 @@ describe('Actions Plugin', () => { it('should handle preconfiguredAlertHistoryEsIndex = true', async () => { setup(getConfig({ preconfiguredAlertHistoryEsIndex: true })); - await plugin.setup(coreSetup, pluginsSetup); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const pluginSetup = await plugin.setup(coreSetup as any, pluginsSetup); + pluginSetup.registerType({ + id: '.index', + name: 'ES Index', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + executor, + }); + const pluginStart = await plugin.start(coreStart, pluginsStart); expect(pluginStart.preconfiguredActions.length).toEqual(2); diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 91e08d4950c61..fa70e0dc71354 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -49,7 +49,6 @@ import { createEphemeralExecutionEnqueuerFunction, createBulkExecutionEnqueuerFunction, } from './create_execute_function'; -import { registerBuiltInActionTypes } from './builtin_action_types'; import { registerActionsUsageCollector } from './usage'; import { ActionExecutor, @@ -92,12 +91,12 @@ import { getAlertHistoryEsIndex } from './preconfigured_connectors/alert_history import { createAlertHistoryIndexTemplate } from './preconfigured_connectors/alert_history_es_index/create_alert_history_index_template'; import { ACTIONS_FEATURE_ID, AlertHistoryEsIndexConnectorId } from '../common'; import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER } from './constants/event_log'; -import { ConnectorTokenClient } from './builtin_action_types/lib/connector_token_client'; +import { ConnectorTokenClient } from './lib/connector_token_client'; import { InMemoryMetrics, registerClusterCollector, registerNodeCollector } from './monitoring'; import { isConnectorDeprecated, ConnectorWithOptionalDeprecation, -} from './lib/is_conector_deprecated'; +} from './lib/is_connector_deprecated'; import { createSubActionConnectorFramework } from './sub_action_framework'; import { IServiceAbstract, SubActionConnectorType } from './sub_action_framework/types'; import { SubActionConnector } from './sub_action_framework/sub_action_connector'; @@ -275,13 +274,6 @@ export class ActionsPlugin implements Plugin { return Object.freeze({ name: i18n.translate('xpack.actions.alertHistoryEsIndexConnector.name', { diff --git a/x-pack/plugins/actions/server/routes/get_well_known_email_service.test.ts b/x-pack/plugins/actions/server/routes/get_well_known_email_service.test.ts deleted file mode 100644 index 26048c25ce157..0000000000000 --- a/x-pack/plugins/actions/server/routes/get_well_known_email_service.test.ts +++ /dev/null @@ -1,175 +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 { getWellKnownEmailServiceRoute } from './get_well_known_email_service'; -import { httpServiceMock } from '@kbn/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './legacy/_mock_handler_arguments'; -import { verifyAccessAndContext } from './verify_access_and_context'; - -jest.mock('./verify_access_and_context', () => ({ - verifyAccessAndContext: jest.fn(), -})); - -beforeEach(() => { - jest.resetAllMocks(); - (verifyAccessAndContext as jest.Mock).mockImplementation((license, handler) => handler); -}); - -describe('getWellKnownEmailServiceRoute', () => { - it('returns config for well known email service', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - getWellKnownEmailServiceRoute(router, licenseState); - - const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot( - `"/internal/actions/connector/_email_config/{service}"` - ); - - const [context, req, res] = mockHandlerArguments( - {}, - { - params: { service: 'gmail' }, - }, - ['ok'] - ); - - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "host": "smtp.gmail.com", - "port": 465, - "secure": true, - }, - } - `); - - expect(res.ok).toHaveBeenCalledWith({ - body: { - host: 'smtp.gmail.com', - port: 465, - secure: true, - }, - }); - }); - - it('returns config for elastic cloud email service', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - getWellKnownEmailServiceRoute(router, licenseState); - - const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot( - `"/internal/actions/connector/_email_config/{service}"` - ); - - const [context, req, res] = mockHandlerArguments( - {}, - { - params: { service: 'elastic_cloud' }, - }, - ['ok'] - ); - - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "host": "dockerhost", - "port": 10025, - "secure": false, - }, - } - `); - - expect(res.ok).toHaveBeenCalledWith({ - body: { - host: 'dockerhost', - port: 10025, - secure: false, - }, - }); - }); - - it('returns empty for unknown service', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - getWellKnownEmailServiceRoute(router, licenseState); - - const [config, handler] = router.get.mock.calls[0]; - expect(config.path).toMatchInlineSnapshot( - `"/internal/actions/connector/_email_config/{service}"` - ); - - const [context, req, res] = mockHandlerArguments( - {}, - { - params: { service: 'foo' }, - }, - ['ok'] - ); - - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object {}, - } - `); - - expect(res.ok).toHaveBeenCalledWith({ - body: {}, - }); - }); - - it('ensures the license allows getting well known email service config', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - getWellKnownEmailServiceRoute(router, licenseState); - - const [, handler] = router.get.mock.calls[0]; - - const [context, req, res] = mockHandlerArguments( - {}, - { - params: { service: 'gmail' }, - }, - ['ok'] - ); - - await handler(context, req, res); - - expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); - }); - - it('ensures the license check prevents getting well known email service config', async () => { - const licenseState = licenseStateMock.create(); - const router = httpServiceMock.createRouter(); - - (verifyAccessAndContext as jest.Mock).mockImplementation(() => async () => { - throw new Error('OMG'); - }); - - getWellKnownEmailServiceRoute(router, licenseState); - - const [, handler] = router.get.mock.calls[0]; - - const [context, req, res] = mockHandlerArguments( - {}, - { - params: { service: 'gmail' }, - }, - ['ok'] - ); - - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); - - expect(verifyAccessAndContext).toHaveBeenCalledWith(licenseState, expect.any(Function)); - }); -}); diff --git a/x-pack/plugins/actions/server/routes/get_well_known_email_service.ts b/x-pack/plugins/actions/server/routes/get_well_known_email_service.ts deleted file mode 100644 index 9a74bd7afc90e..0000000000000 --- a/x-pack/plugins/actions/server/routes/get_well_known_email_service.ts +++ /dev/null @@ -1,57 +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 { schema } from '@kbn/config-schema'; -import { IRouter } from '@kbn/core/server'; -import nodemailerGetService from 'nodemailer/lib/well-known'; -import SMTPConnection from 'nodemailer/lib/smtp-connection'; -import { ILicenseState } from '../lib'; -import { AdditionalEmailServices, INTERNAL_BASE_ACTION_API_PATH } from '../../common'; -import { ActionsRequestHandlerContext } from '../types'; -import { verifyAccessAndContext } from './verify_access_and_context'; -import { ELASTIC_CLOUD_SERVICE } from '../builtin_action_types/email'; - -const paramSchema = schema.object({ - service: schema.string(), -}); - -export const getWellKnownEmailServiceRoute = ( - router: IRouter, - licenseState: ILicenseState -) => { - router.get( - { - path: `${INTERNAL_BASE_ACTION_API_PATH}/connector/_email_config/{service}`, - validate: { - params: paramSchema, - }, - }, - router.handleLegacyErrors( - verifyAccessAndContext(licenseState, async function (context, req, res) { - const { service } = req.params; - - let response: SMTPConnection.Options = {}; - if (service === AdditionalEmailServices.ELASTIC_CLOUD) { - response = ELASTIC_CLOUD_SERVICE; - } else { - const serviceEntry = nodemailerGetService(service); - if (serviceEntry) { - response = { - host: serviceEntry.host, - port: serviceEntry.port, - secure: serviceEntry.secure, - }; - } - } - - return res.ok({ - body: response, - }); - }) - ) - ); -}; diff --git a/x-pack/plugins/actions/server/routes/index.ts b/x-pack/plugins/actions/server/routes/index.ts index 501d7045e6d92..9ff98e1ee940b 100644 --- a/x-pack/plugins/actions/server/routes/index.ts +++ b/x-pack/plugins/actions/server/routes/index.ts @@ -16,7 +16,6 @@ import { getActionRoute } from './get'; import { getAllActionRoute } from './get_all'; import { connectorTypesRoute } from './connector_types'; import { updateActionRoute } from './update'; -import { getWellKnownEmailServiceRoute } from './get_well_known_email_service'; import { getOAuthAccessToken } from './get_oauth_access_token'; import { defineLegacyRoutes } from './legacy'; import { ActionsConfigurationUtilities } from '../actions_config'; @@ -42,5 +41,4 @@ export function defineRoutes(opts: RouteOptions) { executeActionRoute(router, licenseState); getOAuthAccessToken(router, licenseState, actionsConfigUtils); - getWellKnownEmailServiceRoute(router, licenseState); } diff --git a/x-pack/plugins/actions/server/saved_objects/transform_connectors_for_export.test.ts b/x-pack/plugins/actions/server/saved_objects/transform_connectors_for_export.test.ts index 3524d4ae37867..bbe8009566e94 100644 --- a/x-pack/plugins/actions/server/saved_objects/transform_connectors_for_export.test.ts +++ b/x-pack/plugins/actions/server/saved_objects/transform_connectors_for_export.test.ts @@ -6,36 +6,26 @@ */ import { transformConnectorsForExport } from './transform_connectors_for_export'; -import { ActionTypeRegistry, ActionTypeRegistryOpts } from '../action_type_registry'; -import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { actionsConfigMock } from '../actions_config.mock'; -import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; -import { ActionExecutor, TaskRunnerFactory } from '../lib'; -import { registerBuiltInActionTypes } from '../builtin_action_types'; -import { inMemoryMetricsMock } from '../monitoring/in_memory_metrics.mock'; +import { actionTypeRegistryMock } from '../action_type_registry.mock'; +import { ActionType, ActionTypeRegistryContract, ActionTypeSecrets } from '../types'; describe('transform connector for export', () => { - const inMemoryMetrics = inMemoryMetricsMock.create(); - const actionTypeRegistryParams: ActionTypeRegistryOpts = { - licensing: licensingMock.createSetup(), - taskManager: taskManagerMock.createSetup(), - taskRunnerFactory: new TaskRunnerFactory( - new ActionExecutor({ isESOCanEncrypt: true }), - inMemoryMetrics - ), - actionsConfigUtils: actionsConfigMock.create(), - licenseState: licenseStateMock.create(), - preconfiguredActions: [], + const connectorType: jest.Mocked = { + id: 'test', + name: 'Test', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + executor: jest.fn(), + validate: { + secrets: { + schema: { + validate: (value: unknown) => value as ActionTypeSecrets, + }, + }, + }, }; - const actionTypeRegistry: ActionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); - - registerBuiltInActionTypes({ - logger: loggingSystemMock.create().get(), - actionTypeRegistry, - actionsConfigUtils: actionsConfigMock.create(), - }); + const actionTypeRegistry: jest.Mocked = + actionTypeRegistryMock.create(); const connectorsWithNoSecrets = [ { @@ -239,6 +229,7 @@ describe('transform connector for export', () => { ]; it('should not change connectors without secrets', () => { + actionTypeRegistry.get.mockReturnValue(connectorType); expect(transformConnectorsForExport(connectorsWithNoSecrets, actionTypeRegistry)).toEqual( connectorsWithNoSecrets.map((connector) => ({ ...connector, @@ -251,6 +242,18 @@ describe('transform connector for export', () => { }); it('should remove secrets for connectors with secrets', () => { + actionTypeRegistry.get.mockReturnValue({ + ...connectorType, + validate: { + secrets: { + schema: { + validate: (value: unknown) => { + throw new Error('i need secrets!'); + }, + }, + }, + }, + }); expect(transformConnectorsForExport(connectorsWithSecrets, actionTypeRegistry)).toEqual( connectorsWithSecrets.map((connector) => ({ ...connector, diff --git a/x-pack/plugins/actions/server/saved_objects/transform_connectors_for_export.ts b/x-pack/plugins/actions/server/saved_objects/transform_connectors_for_export.ts index 34ff241c36852..111d5dd12d4ab 100644 --- a/x-pack/plugins/actions/server/saved_objects/transform_connectors_for_export.ts +++ b/x-pack/plugins/actions/server/saved_objects/transform_connectors_for_export.ts @@ -7,13 +7,12 @@ import { SavedObject } from '@kbn/core/server'; import { ActionsConfigurationUtilities } from '../actions_config'; -import { ActionTypeRegistry } from '../action_type_registry'; import { validateSecrets } from '../lib'; -import { RawAction, ActionType } from '../types'; +import { RawAction, ActionType, ActionTypeRegistryContract } from '../types'; export function transformConnectorsForExport( connectors: SavedObject[], - actionTypeRegistry: ActionTypeRegistry + actionTypeRegistry: ActionTypeRegistryContract ): Array> { return connectors.map((c) => { const connector = c as SavedObject; diff --git a/x-pack/plugins/actions/server/sub_action_framework/executor.test.ts b/x-pack/plugins/actions/server/sub_action_framework/executor.test.ts index d02a054360be7..5fc07c4b6f236 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/executor.test.ts +++ b/x-pack/plugins/actions/server/sub_action_framework/executor.test.ts @@ -64,6 +64,7 @@ describe('Executor', () => { config, secrets, services, + configurationUtilities: mockedActionsConfig, }); expect(res).toEqual({ @@ -84,6 +85,7 @@ describe('Executor', () => { config, secrets, services, + configurationUtilities: mockedActionsConfig, }); expect(res).toEqual({ @@ -104,6 +106,7 @@ describe('Executor', () => { config, secrets, services, + configurationUtilities: mockedActionsConfig, }); expect(res).toEqual({ @@ -122,6 +125,7 @@ describe('Executor', () => { config, secrets, services, + configurationUtilities: mockedActionsConfig, }); expect(res).toEqual({ @@ -135,7 +139,14 @@ describe('Executor', () => { const executor = createExecutor(TestNoSubActions); await expect(async () => - executor({ actionId, params, config, secrets, services }) + executor({ + actionId, + params, + config, + secrets, + services, + configurationUtilities: mockedActionsConfig, + }) ).rejects.toThrowError('You should register at least one subAction for your connector type'); }); @@ -149,6 +160,7 @@ describe('Executor', () => { config, secrets, services, + configurationUtilities: mockedActionsConfig, }) ).rejects.toThrowError( 'Sub action "not-exist" is not registered. Connector id: test-action-id. Connector name: Test. Connector type: .test' @@ -165,6 +177,7 @@ describe('Executor', () => { config, secrets, services, + configurationUtilities: mockedActionsConfig, }) ).rejects.toThrowError( 'Method "not-exist" does not exists in service. Sub action: "testUrl". Connector id: test-action-id. Connector name: Test. Connector type: .test' @@ -181,6 +194,7 @@ describe('Executor', () => { config, secrets, services, + configurationUtilities: mockedActionsConfig, }) ).rejects.toThrowError( 'Method "notAFunction" must be a function. Connector id: test-action-id. Connector name: Test. Connector type: .test' @@ -191,7 +205,14 @@ describe('Executor', () => { const executor = createExecutor(TestExecutor); await expect(async () => - executor({ actionId, params: { ...params, subAction: 'echo' }, config, secrets, services }) + executor({ + actionId, + params: { ...params, subAction: 'echo' }, + config, + secrets, + services, + configurationUtilities: mockedActionsConfig, + }) ).rejects.toThrowError( 'Request validation failed (Error: [id]: expected value of type [string] but got [undefined])' ); diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index d809228e4eeda..ae344d4f62dbc 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -20,14 +20,10 @@ import { PluginSetupContract, PluginStartContract } from './plugin'; import { ActionsClient } from './actions_client'; import { ActionTypeExecutorResult } from '../common'; import { TaskInfo } from './lib/action_executor'; -import { ConnectorTokenClient } from './builtin_action_types/lib/connector_token_client'; +import { ConnectorTokenClient } from './lib/connector_token_client'; import { ActionsConfigurationUtilities } from './actions_config'; export type { ActionTypeExecutorResult, ActionTypeExecutorRawResult } from '../common'; -export type { GetFieldsByIssueTypeResponse as JiraGetFieldsResponse } from './builtin_action_types/jira/types'; -export type { GetCommonFieldsResponse as ServiceNowGetFieldsResponse } from './builtin_action_types/servicenow/types'; -export type { GetCommonFieldsResponse as ResilientGetFieldsResponse } from './builtin_action_types/resilient/types'; -export type { SwimlanePublicConfigurationType } from './builtin_action_types/swimlane/types'; export type WithoutQueryAndParams = Pick>; export type GetServicesFunction = (request: KibanaRequest) => Services; export type ActionTypeRegistryContract = PublicMethodsOf; @@ -66,6 +62,7 @@ export interface ActionTypeExecutorOptions { params: Params; isEphemeral?: boolean; taskInfo?: TaskInfo; + configurationUtilities: ActionsConfigurationUtilities; } export interface ActionResult { diff --git a/x-pack/plugins/aiops/common/api/log_categorization/index.ts b/x-pack/plugins/aiops/common/api/log_categorization/index.ts new file mode 100644 index 0000000000000..cff9bb82c6d83 --- /dev/null +++ b/x-pack/plugins/aiops/common/api/log_categorization/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { categorizeSchema } from './schema'; +export type { CategorizeSchema } from './schema'; diff --git a/x-pack/plugins/aiops/common/api/log_categorization/schema.ts b/x-pack/plugins/aiops/common/api/log_categorization/schema.ts new file mode 100644 index 0000000000000..5c8387bfe5995 --- /dev/null +++ b/x-pack/plugins/aiops/common/api/log_categorization/schema.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 { schema, TypeOf } from '@kbn/config-schema'; + +export const categorizeSchema = schema.object({ + index: schema.string(), + field: schema.string(), + timeField: schema.string(), + to: schema.number(), + from: schema.number(), + query: schema.any(), + intervalMs: schema.maybe(schema.number()), +}); + +export type CategorizeSchema = TypeOf; diff --git a/x-pack/plugins/aiops/public/application/utils/error_utils.ts b/x-pack/plugins/aiops/public/application/utils/error_utils.ts index ab2843ae6c3e2..f1f1c34dd2959 100644 --- a/x-pack/plugins/aiops/public/application/utils/error_utils.ts +++ b/x-pack/plugins/aiops/public/application/utils/error_utils.ts @@ -185,3 +185,9 @@ export const extractErrorProperties = (error: ErrorType): AiOpsErrorObject => { message: '', }; }; + +export const extractErrorMessage = (error: ErrorType): string => { + // extract only the error message within the response error coming from Kibana, Elasticsearch, and our own ML messages + const errorObj = extractErrorProperties(error); + return errorObj.message; +}; diff --git a/x-pack/plugins/aiops/public/components/document_count_content/document_count_chart/document_count_chart.tsx b/x-pack/plugins/aiops/public/components/document_count_content/document_count_chart/document_count_chart.tsx index 5f024ac393c50..1989316b15ec1 100644 --- a/x-pack/plugins/aiops/public/components/document_count_content/document_count_chart/document_count_chart.tsx +++ b/x-pack/plugins/aiops/public/components/document_count_content/document_count_chart/document_count_chart.tsx @@ -27,7 +27,6 @@ import { DualBrush, DualBrushAnnotation } from '@kbn/aiops-components'; import { getSnappedWindowParameters, getWindowParameters } from '@kbn/aiops-utils'; import type { WindowParameters } from '@kbn/aiops-utils'; import { MULTILAYER_TIME_AXIS_STYLE } from '@kbn/charts-plugin/common'; -import type { ChangePoint } from '@kbn/ml-agg-utils'; import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context'; @@ -48,14 +47,14 @@ export interface DocumentCountChartPoint { } interface DocumentCountChartProps { - brushSelectionUpdateHandler: (d: WindowParameters, force: boolean) => void; + brushSelectionUpdateHandler?: (d: WindowParameters, force: boolean) => void; width?: number; chartPoints: DocumentCountChartPoint[]; chartPointsSplit?: DocumentCountChartPoint[]; timeRangeEarliest: number; timeRangeLatest: number; interval: number; - changePoint?: ChangePoint; + chartPointsSplitLabel: string; isBrushCleared: boolean; } @@ -100,7 +99,7 @@ export const DocumentCountChart: FC = ({ timeRangeEarliest, timeRangeLatest, interval, - changePoint, + chartPointsSplitLabel, isBrushCleared, }) => { const { data, uiSettings, fieldFormats, charts } = useAiopsAppContext(); @@ -125,8 +124,6 @@ export const DocumentCountChart: FC = ({ } ); - const splitSeriesName = `${changePoint?.fieldName}:${changePoint?.fieldValue}`; - // TODO Let user choose between ZOOM and BRUSH mode. const [viewMode] = useState(VIEW_MODE.BRUSH); @@ -198,6 +195,9 @@ export const DocumentCountChart: FC = ({ }; const onElementClick: ElementClickListener = ([elementData]) => { + if (brushSelectionUpdateHandler === undefined) { + return; + } const startRange = (elementData as XYChartElementEvent)[0].x; const range = { @@ -245,6 +245,9 @@ export const DocumentCountChart: FC = ({ }, [isBrushCleared, originalWindowParameters]); function onWindowParametersChange(wp: WindowParameters, wpPx: WindowParameters) { + if (brushSelectionUpdateHandler === undefined) { + return; + } setWindowParameters(wp); setWindowParametersAsPixels(wpPx); brushSelectionUpdateHandler(wp, false); @@ -360,7 +363,7 @@ export const DocumentCountChart: FC = ({ {chartPointsSplit && ( = ({ const bucketTimestamps = Object.keys(documentCountStats?.buckets ?? {}).map((time) => +time); const timeRangeEarliest = min(bucketTimestamps); const timeRangeLatest = max(bucketTimestamps); + const chartPointsSplitLabel = useMemo( + () => `${changePoint?.fieldName}:${changePoint?.fieldValue}`, + [changePoint] + ); if ( documentCountStats === undefined || @@ -118,7 +122,7 @@ export const DocumentCountContent: FC = ({ timeRangeEarliest={timeRangeEarliest} timeRangeLatest={timeRangeLatest} interval={documentCountStats.interval} - changePoint={changePoint} + chartPointsSplitLabel={chartPointsSplitLabel} isBrushCleared={isBrushCleared} /> )} diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx b/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx new file mode 100644 index 0000000000000..1aa613962ff9e --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx @@ -0,0 +1,270 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { TimefilterContract } from '@kbn/data-plugin/public'; +import { + EuiButton, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiBasicTableColumn, + EuiCode, + EuiText, + EuiTableSelectionType, +} from '@elastic/eui'; + +import { useDiscoverLinks } from '../use_discover_links'; +import { MiniHistogram } from '../../mini_histogram'; +import { useEuiTheme } from '../../../hooks/use_eui_theme'; +import type { AiOpsIndexBasedAppState } from '../../explain_log_rate_spikes/explain_log_rate_spikes_app_state'; +import type { EventRate, Category, SparkLinesPerCategory } from '../use_categorize_request'; +import { useTableState } from './use_table_state'; + +const QUERY_MODE = { + INCLUDE: 'should', + EXCLUDE: 'must_not', +} as const; +export type QueryMode = typeof QUERY_MODE[keyof typeof QUERY_MODE]; + +interface Props { + categories: Category[]; + sparkLines: SparkLinesPerCategory; + eventRate: EventRate; + dataViewId: string; + selectedField: string | undefined; + timefilter: TimefilterContract; + aiopsListState: Required; + pinnedCategory: Category | null; + setPinnedCategory: (category: Category | null) => void; + selectedCategory: Category | null; + setSelectedCategory: (category: Category | null) => void; +} + +export const CategoryTable: FC = ({ + categories, + sparkLines, + eventRate, + dataViewId, + selectedField, + timefilter, + aiopsListState, + pinnedCategory, + setPinnedCategory, + selectedCategory, + setSelectedCategory, +}) => { + const euiTheme = useEuiTheme(); + const { openInDiscoverWithFilter } = useDiscoverLinks(); + const [selectedCategories, setSelectedCategories] = useState([]); + const { onTableChange, pagination, sorting } = useTableState(categories ?? [], 'key'); + + const openInDiscover = (mode: QueryMode, category?: Category) => { + const timefilterActiveBounds = timefilter.getActiveBounds(); + if (timefilterActiveBounds === undefined || selectedField === undefined) { + return; + } + + openInDiscoverWithFilter( + dataViewId, + selectedField, + selectedCategories, + aiopsListState, + timefilterActiveBounds, + mode, + category + ); + }; + + const columns: Array> = [ + { + field: 'count', + name: i18n.translate('xpack.aiops.logCategorization.column.count', { + defaultMessage: 'Count', + }), + sortable: true, + width: '80px', + }, + { + field: 'count', + name: i18n.translate('xpack.aiops.logCategorization.column.logRate', { + defaultMessage: 'Log rate', + }), + sortable: true, + width: '100px', + render: (_, { key }) => { + const sparkLine = sparkLines[key]; + if (sparkLine === undefined) { + return null; + } + const histogram = eventRate.map((e) => ({ + doc_count_overall: e.docCount, + doc_count_change_point: sparkLine[e.key], + key: e.key, + key_as_string: `${e.key}`, + })); + + return ( + + ); + }, + }, + { + field: 'examples', + name: i18n.translate('xpack.aiops.logCategorization.column.examples', { + defaultMessage: 'Examples', + }), + sortable: true, + style: { display: 'block' }, + render: (examples: string[]) => ( +
+ {examples.map((e) => ( + <> + + + {e} + + + + + ))} +
+ ), + }, + { + name: 'Actions', + width: '60px', + actions: [ + { + name: i18n.translate('xpack.aiops.logCategorization.showInDiscover', { + defaultMessage: 'Show these in Discover', + }), + description: i18n.translate('xpack.aiops.logCategorization.showInDiscover', { + defaultMessage: 'Show these in Discover', + }), + icon: 'discoverApp', + type: 'icon', + onClick: (category) => openInDiscover(QUERY_MODE.INCLUDE, category), + }, + { + name: i18n.translate('xpack.aiops.logCategorization.filterOutInDiscover', { + defaultMessage: 'Filter out in Discover', + }), + description: i18n.translate('xpack.aiops.logCategorization.filterOutInDiscover', { + defaultMessage: 'Filter out in Discover', + }), + icon: 'filter', + type: 'icon', + onClick: (category) => openInDiscover(QUERY_MODE.EXCLUDE, category), + }, + // Disabled for now + // { + // name: i18n.translate('xpack.aiops.logCategorization.openInDataViz', { + // defaultMessage: 'Open in data visualizer', + // }), + // icon: 'stats', + // type: 'icon', + // onClick: () => {}, + // }, + ], + }, + ] as Array>; + + const selectionValue: EuiTableSelectionType | undefined = { + selectable: () => true, + onSelectionChange: (selectedItems) => setSelectedCategories(selectedItems), + }; + + const getRowStyle = (category: Category) => { + if ( + pinnedCategory && + pinnedCategory.key === category.key && + pinnedCategory.key === category.key + ) { + return { + backgroundColor: 'rgb(227,240,249,0.37)', + }; + } + + if ( + selectedCategory && + selectedCategory.key === category.key && + selectedCategory.key === category.key + ) { + return { + backgroundColor: euiTheme.euiColorLightestShade, + }; + } + + return { + backgroundColor: 'white', + }; + }; + + return ( + <> + {selectedCategories.length > 0 ? ( + <> + + + openInDiscover(QUERY_MODE.INCLUDE)}> + + + + + openInDiscover(QUERY_MODE.EXCLUDE)}> + + + + + + ) : null} + + compressed + items={categories} + columns={columns} + isSelectable={true} + selection={selectionValue} + itemId="key" + onTableChange={onTableChange} + pagination={pagination} + sorting={sorting} + rowProps={(category) => { + return { + onClick: () => { + if (category.key === pinnedCategory?.key) { + setPinnedCategory(null); + } else { + setPinnedCategory(category); + } + }, + onMouseEnter: () => { + setSelectedCategory(category); + }, + onMouseLeave: () => { + setSelectedCategory(null); + }, + style: getRowStyle(category), + }; + }} + /> + + ); +}; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_table/index.ts b/x-pack/plugins/aiops/public/components/log_categorization/category_table/index.ts new file mode 100644 index 0000000000000..65b93b2c1eb44 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_categorization/category_table/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { CategoryTable } from './category_table'; +export type { QueryMode } from './category_table'; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_table/use_table_state.ts b/x-pack/plugins/aiops/public/components/log_categorization/category_table/use_table_state.ts new file mode 100644 index 0000000000000..c67fe7d28fa9b --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_categorization/category_table/use_table_state.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 { useState } from 'react'; +import { EuiInMemoryTable, Direction, Pagination } from '@elastic/eui'; + +export function useTableState(items: T[], initialSortField: string) { + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [sortField, setSortField] = useState(initialSortField); + const [sortDirection, setSortDirection] = useState('asc'); + + const onTableChange: EuiInMemoryTable['onTableChange'] = ({ + page = { index: 0, size: 10 }, + sort = { field: sortField, direction: sortDirection }, + }) => { + const { index, size } = page; + setPageIndex(index); + setPageSize(size); + + const { field, direction } = sort; + setSortField(field as string); + setSortDirection(direction as Direction); + }; + + const pagination: Pagination = { + pageIndex, + pageSize, + totalItemCount: (items ?? []).length, + pageSizeOptions: [10, 20, 50], + showPerPageOptions: true, + }; + + const sorting = { + sort: { + field: sortField, + direction: sortDirection, + }, + }; + + return { onTableChange, pagination, sorting, setPageIndex }; +} diff --git a/x-pack/plugins/aiops/public/components/log_categorization/document_count_chart.tsx b/x-pack/plugins/aiops/public/components/log_categorization/document_count_chart.tsx new file mode 100644 index 0000000000000..4ec9ddd68209e --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_categorization/document_count_chart.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 React, { FC, useMemo } from 'react'; + +import { i18n } from '@kbn/i18n'; +import { DocumentCountChart as DocumentCountChartRoot } from '../document_count_content/document_count_chart'; +import { TotalCountHeader } from '../document_count_content/total_count_header'; +import type { Category, SparkLinesPerCategory } from './use_categorize_request'; +import type { EventRate } from './use_categorize_request'; +import { DocumentCountStats } from '../../get_document_stats'; + +interface Props { + totalCount: number; + pinnedCategory: Category | null; + selectedCategory: Category | null; + eventRate: EventRate; + sparkLines: SparkLinesPerCategory; + documentCountStats?: DocumentCountStats; +} + +export const DocumentCountChart: FC = ({ + eventRate, + sparkLines, + totalCount, + pinnedCategory, + selectedCategory, + documentCountStats, +}) => { + const chartPointsSplitLabel = i18n.translate( + 'xpack.aiops.logCategorization.chartPointsSplitLabel', + { + defaultMessage: 'Selected category', + } + ); + const chartPoints = useMemo(() => { + const category = selectedCategory ?? pinnedCategory ?? null; + return eventRate.map(({ key, docCount }) => { + let value = docCount; + if (category && sparkLines[category.key] && sparkLines[category.key][key]) { + value -= sparkLines[category.key][key]; + } + return { time: key, value }; + }); + }, [eventRate, pinnedCategory, selectedCategory, sparkLines]); + + const chartPointsSplit = useMemo(() => { + const category = selectedCategory ?? pinnedCategory ?? null; + return category !== null + ? eventRate.map(({ key }) => { + const value = + sparkLines && sparkLines[category.key] && sparkLines[category.key][key] + ? sparkLines[category.key][key] + : 0; + return { time: key, value }; + }) + : undefined; + }, [eventRate, pinnedCategory, selectedCategory, sparkLines]); + + if (documentCountStats?.interval === undefined) { + return null; + } + + return ( + <> + + + + ); +}; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/index.ts b/x-pack/plugins/aiops/public/components/log_categorization/index.ts new file mode 100644 index 0000000000000..748a0f8486420 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_categorization/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type { LogCategorizationAppStateProps } from './log_categorization_app_state'; +import { LogCategorizationAppState } from './log_categorization_app_state'; + +// required for dynamic import using React.lazy() +// eslint-disable-next-line import/no-default-export +export default LogCategorizationAppState; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_app_state.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_app_state.tsx new file mode 100644 index 0000000000000..1a546f5ec60bf --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_app_state.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, { FC } from 'react'; +import type { SavedSearch } from '@kbn/discover-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { LogCategorizationPage } from './log_categorization_page'; +import { SavedSearchSavedObject } from '../../application/utils/search_utils'; +import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context'; +import { AiopsAppContext } from '../../hooks/use_aiops_app_context'; + +export interface LogCategorizationAppStateProps { + dataView: DataView; + savedSearch: SavedSearch | SavedSearchSavedObject | null; + appDependencies: AiopsAppDependencies; +} + +export const LogCategorizationAppState: FC = ({ + dataView, + savedSearch, + appDependencies, +}) => { + return ( + + + + ); +}; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx new file mode 100644 index 0000000000000..6bc90db0a7b71 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx @@ -0,0 +1,333 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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, useState, useEffect, useCallback, useMemo } from 'react'; +import type { SavedSearch } from '@kbn/discover-plugin/public'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { Filter, Query } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiButton, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiPageBody, + EuiPageContentHeader_Deprecated as EuiPageContentHeader, + EuiPageContentHeaderSection_Deprecated as EuiPageContentHeaderSection, + EuiTitle, + EuiComboBox, + EuiComboBoxOptionOption, + EuiFormRow, + EuiLoadingContent, +} from '@elastic/eui'; + +import { FullTimeRangeSelector } from '../full_time_range_selector'; +import { DatePickerWrapper } from '../date_picker_wrapper'; +import { useData } from '../../hooks/use_data'; +import { SearchPanel } from '../search_panel'; +import type { + SearchQueryLanguage, + SavedSearchSavedObject, +} from '../../application/utils/search_utils'; +import { useUrlState } from '../../hooks/use_url_state'; +import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; +import { restorableDefaults } from '../explain_log_rate_spikes/explain_log_rate_spikes_app_state'; +import { useCategorizeRequest } from './use_categorize_request'; +import type { EventRate, Category, SparkLinesPerCategory } from './use_categorize_request'; +import { CategoryTable } from './category_table'; +import { DocumentCountChart } from './document_count_chart'; + +export interface LogCategorizationPageProps { + dataView: DataView; + savedSearch: SavedSearch | SavedSearchSavedObject | null; +} + +const BAR_TARGET = 20; + +export const LogCategorizationPage: FC = ({ + dataView, + savedSearch, +}) => { + const { + notifications: { toasts }, + } = useAiopsAppContext(); + + const { runCategorizeRequest, cancelRequest } = useCategorizeRequest(); + const [aiopsListState, setAiopsListState] = useState(restorableDefaults); + const [globalState, setGlobalState] = useUrlState('_g'); + const [selectedField, setSelectedField] = useState(); + const [selectedCategory, setSelectedCategory] = useState(null); + const [categories, setCategories] = useState(null); + const [currentSavedSearch, setCurrentSavedSearch] = useState(savedSearch); + const [loading, setLoading] = useState(false); + const [totalCount, setTotalCount] = useState(0); + const [eventRate, setEventRate] = useState([]); + const [pinnedCategory, setPinnedCategory] = useState(null); + const [sparkLines, setSparkLines] = useState({}); + + useEffect( + function cancelRequestOnLeave() { + return () => { + cancelRequest(); + }; + }, + [cancelRequest] + ); + + const setSearchParams = useCallback( + (searchParams: { + searchQuery: Query['query']; + searchString: Query['query']; + queryLanguage: SearchQueryLanguage; + filters: Filter[]; + }) => { + // When the user loads saved search and then clear or modify the query + // we should remove the saved search and replace it with the index pattern id + if (currentSavedSearch !== null) { + setCurrentSavedSearch(null); + } + + setAiopsListState({ + ...aiopsListState, + searchQuery: searchParams.searchQuery, + searchString: searchParams.searchString, + searchQueryLanguage: searchParams.queryLanguage, + filters: searchParams.filters, + }); + }, + [currentSavedSearch, aiopsListState, setAiopsListState] + ); + + const { + documentStats, + timefilter, + earliest, + latest, + searchQueryLanguage, + searchString, + searchQuery, + intervalMs, + } = useData( + { currentDataView: dataView, currentSavedSearch }, + aiopsListState, + setGlobalState, + undefined, + BAR_TARGET + ); + + 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]); + + const fields = useMemo( + () => + dataView.fields + .filter( + ({ displayName, esTypes, count }) => + esTypes && esTypes.includes('text') && !['_id', '_index'].includes(displayName) + ) + .map(({ displayName }) => ({ + label: displayName, + })), + [dataView] + ); + + useEffect( + function setSingleFieldAsSelected() { + if (fields.length === 1) { + setSelectedField(fields[0].label); + } + }, + [fields] + ); + + useEffect(() => { + if (documentStats.documentCountStats?.buckets) { + setEventRate( + Object.entries(documentStats.documentCountStats.buckets).map(([key, docCount]) => ({ + key: +key, + docCount, + })) + ); + setTotalCount(documentStats.totalCount); + } + }, [documentStats, earliest, latest, searchQueryLanguage, searchString, searchQuery]); + + const loadCategories = useCallback(async () => { + setLoading(true); + setCategories(null); + const { title: index, timeFieldName: timeField } = dataView; + + if (selectedField === undefined || timeField === undefined) { + return; + } + + cancelRequest(); + + try { + const resp = await runCategorizeRequest( + index, + selectedField, + timeField, + earliest, + latest, + searchQuery, + intervalMs + ); + + setCategories(resp.categories); + setSparkLines(resp.sparkLinesPerCategory); + } catch (error) { + toasts.addError(error, { + title: i18n.translate('xpack.aiops.logCategorization.errorLoadingCategories', { + defaultMessage: 'Error loading categories', + }), + }); + } + + setLoading(false); + }, [ + selectedField, + dataView, + searchQuery, + earliest, + latest, + runCategorizeRequest, + cancelRequest, + intervalMs, + toasts, + ]); + + const onFieldChange = (value: EuiComboBoxOptionOption[] | undefined) => { + setSelectedField(value && value.length ? value[0].label : undefined); + }; + + return ( + + + + + +
+ +

{dataView.getName()}

+
+
+
+ + + {dataView.timeFieldName !== undefined && ( + + + + )} + + + + +
+
+
+ + + + + + + + + + + + + + + {loading === false ? ( + { + loadCategories(); + }} + > + + + ) : ( + cancelRequest()}>Cancel + )} + + + + + + {eventRate.length ? ( + <> + + + + + ) : null} + {loading === true ? : null} + {categories !== null ? ( + + ) : null} +
+ ); +}; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/use_categorize_request.ts b/x-pack/plugins/aiops/public/components/log_categorization/use_categorize_request.ts new file mode 100644 index 0000000000000..b687d5a0110d9 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_categorization/use_categorize_request.ts @@ -0,0 +1,201 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { cloneDeep, get } from 'lodash'; +import { useRef, useCallback } from 'react'; +import { isCompleteResponse, isErrorResponse } from '@kbn/data-plugin/public'; +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; + +import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; + +const CATEGORY_LIMIT = 1000; +const EXAMPLE_LIMIT = 1; + +interface CatResponse { + rawResponse: { + aggregations: { + categories: { + buckets: Array<{ + key: string; + doc_count: number; + hit: { hits: { hits: Array<{ _source: { message: string } }> } }; + sparkline: { buckets: Array<{ key_as_string: string; key: number; doc_count: number }> }; + }>; + }; + }; + }; +} + +export interface Category { + key: string; + count: number; + examples: string[]; + sparkline?: Array<{ doc_count: number; key: number; key_as_string: string }>; +} + +export type EventRate = Array<{ + key: number; + docCount: number; +}>; + +export type SparkLinesPerCategory = Record>; + +export function useCategorizeRequest() { + const { data } = useAiopsAppContext(); + + const abortController = useRef(new AbortController()); + + const runCategorizeRequest = useCallback( + ( + index: string, + field: string, + timeField: string, + from: number | undefined, + to: number | undefined, + query: QueryDslQueryContainer, + intervalMs?: number + ): Promise<{ categories: Category[]; sparkLinesPerCategory: SparkLinesPerCategory }> => { + return new Promise((resolve, reject) => { + data.search + .search, CatResponse>( + createCategoryRequest(index, field, timeField, from, to, query, intervalMs), + { abortSignal: abortController.current.signal } + ) + .subscribe({ + next: (result) => { + if (isCompleteResponse(result)) { + resolve(processCategoryResults(result, field)); + } else if (isErrorResponse(result)) { + reject(result); + } else { + // partial results + // Ignore partial results for now. + // An issue with the search function means partial results are not being returned correctly. + } + }, + error: (error) => { + if (error.name === 'AbortError') { + return resolve({ categories: [], sparkLinesPerCategory: {} }); + } + reject(error); + }, + }); + }); + }, + [data.search] + ); + + const cancelRequest = useCallback(() => { + abortController.current.abort(); + abortController.current = new AbortController(); + }, []); + + return { runCategorizeRequest, cancelRequest }; +} + +function createCategoryRequest( + index: string, + field: string, + timeField: string, + from: number | undefined, + to: number | undefined, + queryIn: QueryDslQueryContainer, + intervalMs?: number +) { + const query = cloneDeep(queryIn); + + if (query.bool === undefined) { + query.bool = {}; + } + if (query.bool.must === undefined) { + query.bool.must = []; + if (query.match_all !== undefined) { + query.bool.must.push({ match_all: query.match_all }); + delete query.match_all; + } + } + if (query.multi_match !== undefined) { + query.bool.should = { + multi_match: query.multi_match, + }; + delete query.multi_match; + } + + (query.bool.must as QueryDslQueryContainer[]).push({ + range: { + [timeField]: { + gte: from, + lte: to, + format: 'epoch_millis', + }, + }, + }); + return { + params: { + index, + size: 0, + body: { + query, + aggs: { + categories: { + categorize_text: { + field, + size: CATEGORY_LIMIT, + }, + aggs: { + hit: { + top_hits: { + size: EXAMPLE_LIMIT, + sort: [timeField], + _source: field, + }, + }, + ...(intervalMs + ? { + sparkline: { + date_histogram: { + field: timeField, + fixed_interval: `${intervalMs}ms`, + }, + }, + } + : {}), + }, + }, + }, + }, + }, + }; +} + +function processCategoryResults(result: CatResponse, field: string) { + const sparkLinesPerCategory: SparkLinesPerCategory = {}; + + if (result.rawResponse.aggregations === undefined) { + throw new Error('processCategoryResults failed, did not return aggregations.'); + } + + const categories: Category[] = result.rawResponse.aggregations.categories.buckets.map((b) => { + sparkLinesPerCategory[b.key] = + b.sparkline === undefined + ? {} + : b.sparkline.buckets.reduce>((acc2, cur2) => { + acc2[cur2.key] = cur2.doc_count; + return acc2; + }, {}); + + return { + key: b.key, + count: b.doc_count, + examples: b.hit.hits.hits.map((h) => get(h._source, field)), + }; + }); + return { + categories, + sparkLinesPerCategory, + }; +} diff --git a/x-pack/plugins/aiops/public/components/log_categorization/use_discover_links.ts b/x-pack/plugins/aiops/public/components/log_categorization/use_discover_links.ts new file mode 100644 index 0000000000000..8a1c438199878 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_categorization/use_discover_links.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import rison from 'rison-node'; +import moment from 'moment'; + +import type { TimeRangeBounds } from '@kbn/data-plugin/common'; +import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; +import type { Category } from './use_categorize_request'; +import type { QueryMode } from './category_table'; +import type { AiOpsIndexBasedAppState } from '../explain_log_rate_spikes/explain_log_rate_spikes_app_state'; + +export function useDiscoverLinks() { + const { + http: { basePath }, + } = useAiopsAppContext(); + + const openInDiscoverWithFilter = ( + index: string, + field: string, + selection: Category[], + aiopsListState: Required, + timefilterActiveBounds: TimeRangeBounds, + mode: QueryMode, + category?: Category + ) => { + const selectedRows = category === undefined ? selection : [category]; + + const _g = rison.encode({ + time: { + from: moment(timefilterActiveBounds.min?.valueOf()).toISOString(), + to: moment(timefilterActiveBounds.max?.valueOf()).toISOString(), + }, + }); + + const _a = rison.encode({ + filters: [ + ...aiopsListState.filters, + { + query: { + bool: { + [mode]: selectedRows.map(({ key: query }) => ({ + match: { + [field]: { + auto_generate_synonyms_phrase_query: false, + fuzziness: 0, + operator: 'and', + query, + }, + }, + })), + }, + }, + }, + ], + index, + interval: 'auto', + query: { + language: aiopsListState.searchQueryLanguage, + query: aiopsListState.searchString, + }, + }); + + let path = basePath.get(); + path += '/app/discover#/'; + path += '?_g=' + _g; + path += '&_a=' + encodeURIComponent(_a); + window.open(path, '_blank'); + }; + + return { openInDiscoverWithFilter }; +} diff --git a/x-pack/plugins/aiops/public/hooks/use_data.ts b/x-pack/plugins/aiops/public/hooks/use_data.ts index 471196334e15c..9ea8f9bd2335b 100644 --- a/x-pack/plugins/aiops/public/hooks/use_data.ts +++ b/x-pack/plugins/aiops/public/hooks/use_data.ts @@ -29,6 +29,8 @@ import { useTimefilter } from './use_time_filter'; import { useDocumentCountStats } from './use_document_count_stats'; import type { Dictionary } from './use_url_state'; +const DEFAULT_BAR_TARGET = 75; + export const useData = ( { currentDataView, @@ -36,7 +38,8 @@ export const useData = ( }: { currentDataView: DataView; currentSavedSearch: SavedSearch | SavedSearchSavedObject | null }, aiopsListState: AiOpsIndexBasedAppState, onUpdate: (params: Dictionary) => void, - selectedChangePoint?: ChangePoint + selectedChangePoint?: ChangePoint, + barTarget: number = DEFAULT_BAR_TARGET ) => { const { uiSettings, @@ -125,10 +128,9 @@ export const useData = ( function updateFieldStatsRequest() { const timefilterActiveBounds = timefilter.getActiveBounds(); if (timefilterActiveBounds !== undefined) { - const BAR_TARGET = 75; _timeBuckets.setInterval('auto'); _timeBuckets.setBounds(timefilterActiveBounds); - _timeBuckets.setBarTarget(BAR_TARGET); + _timeBuckets.setBarTarget(barTarget); setFieldStatsRequest({ earliest: timefilterActiveBounds.min?.valueOf(), latest: timefilterActiveBounds.max?.valueOf(), @@ -186,6 +188,7 @@ export const useData = ( earliest: fieldStatsRequest?.earliest, /** End timestamp filter */ latest: fieldStatsRequest?.latest, + intervalMs: fieldStatsRequest?.intervalMs, searchQueryLanguage, searchString, searchQuery, diff --git a/x-pack/plugins/aiops/public/index.ts b/x-pack/plugins/aiops/public/index.ts index 1f8373839493e..3cd151ea2b72f 100755 --- a/x-pack/plugins/aiops/public/index.ts +++ b/x-pack/plugins/aiops/public/index.ts @@ -13,4 +13,4 @@ export function plugin() { return new AiopsPlugin(); } -export { ExplainLogRateSpikes } from './shared_lazy_components'; +export { ExplainLogRateSpikes, LogCategorization } from './shared_lazy_components'; diff --git a/x-pack/plugins/aiops/public/shared_lazy_components.tsx b/x-pack/plugins/aiops/public/shared_lazy_components.tsx index a84bd359bdb68..90d01f999a8b6 100644 --- a/x-pack/plugins/aiops/public/shared_lazy_components.tsx +++ b/x-pack/plugins/aiops/public/shared_lazy_components.tsx @@ -9,12 +9,13 @@ import React, { FC, Suspense } from 'react'; import { EuiErrorBoundary, EuiLoadingContent } from '@elastic/eui'; import type { ExplainLogRateSpikesAppStateProps } from './components/explain_log_rate_spikes'; +import type { LogCategorizationAppStateProps } from './components/log_categorization'; const ExplainLogRateSpikesAppStateLazy = React.lazy( () => import('./components/explain_log_rate_spikes') ); -const LazyWrapper: FC = ({ children }) => ( +const ExplainLogRateSpikesLazyWrapper: FC = ({ children }) => ( }>{children} @@ -25,7 +26,25 @@ const LazyWrapper: FC = ({ children }) => ( * @param {ExplainLogRateSpikesAppStateProps} props - properties specifying the data on which to run the analysis. */ export const ExplainLogRateSpikes: FC = (props) => ( - + - + +); + +const LogCategorizationAppStateLazy = React.lazy(() => import('./components/log_categorization')); + +const LogCategorizationLazyWrapper: FC = ({ children }) => ( + + }>{children} + +); + +/** + * Lazy-wrapped LogCategorizationAppStateProps React component + * @param {LogCategorizationAppStateProps} props - properties specifying the data on which to run the analysis. + */ +export const LogCategorization: FC = (props) => ( + + + ); diff --git a/x-pack/plugins/aiops/server/plugin.ts b/x-pack/plugins/aiops/server/plugin.ts index 29b4b79884d8c..16e17930d6f2e 100755 --- a/x-pack/plugins/aiops/server/plugin.ts +++ b/x-pack/plugins/aiops/server/plugin.ts @@ -32,7 +32,10 @@ export class AiopsPlugin this.logger = initializerContext.logger.get(); } - public setup(core: CoreSetup, plugins: AiopsPluginSetupDeps) { + public setup( + core: CoreSetup, + plugins: AiopsPluginSetupDeps + ) { this.logger.debug('aiops: Setup'); // Subscribe to license changes and store the current license in `currentLicense`. diff --git a/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts b/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts index 83bcac0bfa70e..f0fadf9476e74 100644 --- a/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts +++ b/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts @@ -7,6 +7,8 @@ import { chunk } from 'lodash'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + import { i18n } from '@kbn/i18n'; import { asyncForEach } from '@kbn/std'; import type { IRouter } from '@kbn/core/server'; @@ -212,6 +214,7 @@ export const defineExplainLogRateSpikesRoute = ( const { fields, df } = await fetchFrequentItems( client, request.body.index, + JSON.parse(request.body.searchQuery) as estypes.QueryDslQueryContainer, changePoints, request.body.timeFieldName, request.body.deviationMin, diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts index fc834e2951db7..02d20ba18795c 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts @@ -25,6 +25,7 @@ function dropDuplicates(cp: ChangePoint[], uniqueFields: string[]) { export async function fetchFrequentItems( client: ElasticsearchClient, index: string, + searchQuery: estypes.QueryDslQueryContainer, changePoints: ChangePoint[], timeFieldName: string, deviationMin: number, @@ -45,7 +46,9 @@ export async function fetchFrequentItems( // TODO add query params const query = { bool: { + minimum_should_match: 2, filter: [ + searchQuery, { range: { [timeFieldName]: { @@ -83,6 +86,7 @@ export async function fetchFrequentItems( fi: { // @ts-expect-error `frequent_items` is not yet part of `AggregationsAggregationContainer` frequent_items: { + minimum_set_size: 2, size: 200, minimum_support: 0.1, fields: aggFields, diff --git a/x-pack/plugins/aiops/server/routes/queries/get_query_with_params.ts b/x-pack/plugins/aiops/server/routes/queries/get_query_with_params.ts index 9e0b82b341d1d..706d2b6aa5c75 100644 --- a/x-pack/plugins/aiops/server/routes/queries/get_query_with_params.ts +++ b/x-pack/plugins/aiops/server/routes/queries/get_query_with_params.ts @@ -7,7 +7,6 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { Query } from '@kbn/es-query'; import type { FieldValuePair } from '@kbn/ml-agg-utils'; import type { AiopsExplainLogRateSpikesSchema } from '../../../common/api/explain_log_rate_spikes'; @@ -23,7 +22,7 @@ interface QueryParams { termFilters?: FieldValuePair[]; } export const getQueryWithParams = ({ params, termFilters }: QueryParams) => { - const searchQuery = JSON.parse(params.searchQuery) as Query['query']; + const searchQuery = JSON.parse(params.searchQuery) as estypes.QueryDslQueryContainer; return { bool: { filter: [ diff --git a/x-pack/plugins/alerting/server/usage/alerting_usage_collector.ts b/x-pack/plugins/alerting/server/usage/alerting_usage_collector.ts index d03faa3aaf65f..6e6eb3cb43f0d 100644 --- a/x-pack/plugins/alerting/server/usage/alerting_usage_collector.ts +++ b/x-pack/plugins/alerting/server/usage/alerting_usage_collector.ts @@ -110,6 +110,18 @@ const byTaskStatusSchemaByType: MakeSchemaFrom['count_failed_and_ unrecognized: byTypeSchema, }; +const byStatusSchema: MakeSchemaFrom['count_rules_by_execution_status'] = { + success: { type: 'long' }, + error: { type: 'long' }, + warning: { type: 'long' }, +}; + +const byNotifyWhenSchema: MakeSchemaFrom['count_rules_by_notify_when'] = { + on_action_group_change: { type: 'long' }, + on_active_alert: { type: 'long' }, + on_throttle_interval: { type: 'long' }, +}; + export function createAlertingUsageCollector( usageCollection: UsageCollectionSetup, taskManager: Promise @@ -173,6 +185,21 @@ export function createAlertingUsageCollector( count_failed_and_unrecognized_rule_tasks_per_day: 0, count_failed_and_unrecognized_rule_tasks_by_status_per_day: {}, count_failed_and_unrecognized_rule_tasks_by_status_by_type_per_day: {}, + count_rules_by_execution_status: { + success: 0, + warning: 0, + error: 0, + }, + count_rules_by_notify_when: { + on_action_group_change: 0, + on_active_alert: 0, + on_throttle_interval: 0, + }, + count_rules_with_tags: 0, + count_rules_snoozed: 0, + count_rules_muted: 0, + count_rules_with_muted_alerts: 0, + count_connector_types_by_consumers: {}, avg_execution_time_per_day: 0, avg_execution_time_by_type_per_day: {}, avg_es_search_duration_per_day: 0, @@ -249,6 +276,13 @@ export function createAlertingUsageCollector( count_failed_and_unrecognized_rule_tasks_per_day: { type: 'long' }, count_failed_and_unrecognized_rule_tasks_by_status_per_day: byTaskStatusSchema, count_failed_and_unrecognized_rule_tasks_by_status_by_type_per_day: byTaskStatusSchemaByType, + count_rules_by_execution_status: byStatusSchema, + count_rules_with_tags: { type: 'long' }, + count_rules_by_notify_when: byNotifyWhenSchema, + count_rules_snoozed: { type: 'long' }, + count_rules_muted: { type: 'long' }, + count_rules_with_muted_alerts: { type: 'long' }, + count_connector_types_by_consumers: { DYNAMIC_KEY: { DYNAMIC_KEY: { type: 'long' } } }, avg_execution_time_per_day: { type: 'long' }, avg_execution_time_by_type_per_day: byTypeSchema, avg_es_search_duration_per_day: { type: 'long' }, diff --git a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts index 79b5d473ebe05..d16b76d91afa9 100644 --- a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts +++ b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.test.ts @@ -55,6 +55,90 @@ describe('kibana index telemetry', () => { }, ], }, + by_execution_status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'unknown', + doc_count: 0, + }, + { + key: 'ok', + doc_count: 1, + }, + { + key: 'active', + doc_count: 2, + }, + { + key: 'pending', + doc_count: 3, + }, + { + key: 'error', + doc_count: 4, + }, + { + key: 'warning', + doc_count: 5, + }, + ], + }, + by_notify_when: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'onActionGroupChange', + doc_count: 5, + }, + { + key: 'onActiveAlert', + doc_count: 6, + }, + { + key: 'onThrottleInterval', + doc_count: 7, + }, + ], + }, + connector_types_by_consumers: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'alerts', + actions: { + connector_types: { + buckets: [ + { + key: '.server-log', + doc_count: 2, + }, + { + key: '.email', + doc_count: 3, + }, + ], + }, + }, + }, + { + key: 'siem', + actions: { + connector_types: { + buckets: [ + { + key: '.index', + doc_count: 4, + }, + ], + }, + }, + }, + ], + }, max_throttle_time: { value: 60 }, min_throttle_time: { value: 0 }, avg_throttle_time: { value: 30 }, @@ -64,6 +148,10 @@ describe('kibana index telemetry', () => { max_actions_count: { value: 4 }, min_actions_count: { value: 0 }, avg_actions_count: { value: 2.5 }, + sum_rules_with_tags: { value: 10 }, + sum_rules_snoozed: { value: 11 }, + sum_rules_muted: { value: 12 }, + sum_rules_with_muted_alerts: { value: 13 }, }, }); @@ -109,6 +197,29 @@ describe('kibana index telemetry', () => { max: 60, min: 0, }, + count_rules_by_execution_status: { + success: 3, + error: 4, + warning: 5, + }, + count_rules_with_tags: 10, + count_rules_by_notify_when: { + on_action_group_change: 5, + on_active_alert: 6, + on_throttle_interval: 7, + }, + count_rules_snoozed: 11, + count_rules_muted: 12, + count_rules_with_muted_alerts: 13, + count_connector_types_by_consumers: { + alerts: { + __email: 3, + '__server-log': 2, + }, + siem: { + __index: 4, + }, + }, }); }); @@ -138,6 +249,20 @@ describe('kibana index telemetry', () => { min: 0, }, count_by_type: {}, + count_rules_by_execution_status: { + success: 0, + error: 0, + warning: 0, + }, + count_rules_with_tags: 0, + count_rules_by_notify_when: { + on_action_group_change: 0, + on_active_alert: 0, + on_throttle_interval: 0, + }, + count_rules_snoozed: 0, + count_rules_muted: 0, + count_rules_with_muted_alerts: 0, count_total: 0, schedule_time: { avg: '0s', @@ -159,6 +284,7 @@ describe('kibana index telemetry', () => { max: 0, min: 0, }, + count_connector_types_by_consumers: {}, }); }); }); diff --git a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.ts b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.ts index 5443fb91e2e1f..0c6d01016c313 100644 --- a/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.ts +++ b/x-pack/plugins/alerting/server/usage/lib/get_telemetry_from_kibana.ts @@ -12,6 +12,13 @@ import type { AggregationsStringTermsBucketKeys, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ElasticsearchClient, Logger } from '@kbn/core/server'; + +import { + ConnectorsByConsumersBucket, + groupConnectorsByConsumers, +} from './group_connectors_by_consumers'; +import { groupRulesByNotifyWhen } from './group_rules_by_notify_when'; +import { groupRulesByStatus } from './group_rules_by_status'; import { AlertingUsage } from '../types'; import { NUM_ALERTING_RULE_TYPES } from '../alerting_usage_collector'; import { parseSimpleRuleTypeBucket } from './parse_simple_rule_type_bucket'; @@ -26,6 +33,13 @@ type GetTotalCountsResults = Pick< AlertingUsage, | 'count_total' | 'count_by_type' + | 'count_rules_by_execution_status' + | 'count_rules_by_notify_when' + | 'count_rules_with_tags' + | 'count_rules_snoozed' + | 'count_rules_muted' + | 'count_rules_with_muted_alerts' + | 'count_connector_types_by_consumers' | 'throttle_time' | 'schedule_time' | 'throttle_time_number_s' @@ -145,6 +159,59 @@ export async function getTotalCountAggregations({ `, }, }, + rule_with_tags: { + type: 'long', + script: { + source: ` + def rule = params._source['alert']; + if (rule != null && rule.tags != null) { + if (rule.tags.size() > 0) { + emit(1); + } else { + emit(0); + } + }`, + }, + }, + rule_snoozed: { + type: 'long', + script: { + source: ` + def rule = params._source['alert']; + if (rule != null && rule.snoozeSchedule != null) { + if (rule.snoozeSchedule.size() > 0) { + emit(1); + } else { + emit(0); + } + }`, + }, + }, + rule_muted: { + type: 'long', + script: { + source: ` + if (doc['alert.muteAll'].value == true) { + emit(1); + } else { + emit(0); + }`, + }, + }, + rule_with_muted_alerts: { + type: 'long', + script: { + source: ` + def rule = params._source['alert']; + if (rule != null && rule.mutedInstanceIds != null) { + if (rule.mutedInstanceIds.size() > 0) { + emit(1); + } else { + emit(0); + } + }`, + }, + }, }, aggs: { by_rule_type_id: { @@ -162,6 +229,39 @@ export async function getTotalCountAggregations({ max_actions_count: { max: { field: 'rule_action_count' } }, min_actions_count: { min: { field: 'rule_action_count' } }, avg_actions_count: { avg: { field: 'rule_action_count' } }, + by_execution_status: { + terms: { + field: 'alert.executionStatus.status', + }, + }, + by_notify_when: { + terms: { + field: 'alert.notifyWhen', + }, + }, + connector_types_by_consumers: { + terms: { + field: 'alert.consumer', + }, + aggs: { + actions: { + nested: { + path: 'alert.actions', + }, + aggs: { + connector_types: { + terms: { + field: 'alert.actions.actionTypeId', + }, + }, + }, + }, + }, + }, + sum_rules_with_tags: { sum: { field: 'rule_with_tags' } }, + sum_rules_snoozed: { sum: { field: 'rule_snoozed' } }, + sum_rules_muted: { sum: { field: 'rule_muted' } }, + sum_rules_with_muted_alerts: { sum: { field: 'rule_with_muted_alerts' } }, }, }, }; @@ -182,15 +282,41 @@ export async function getTotalCountAggregations({ max_actions_count: AggregationsSingleMetricAggregateBase; min_actions_count: AggregationsSingleMetricAggregateBase; avg_actions_count: AggregationsSingleMetricAggregateBase; + by_execution_status: AggregationsTermsAggregateBase; + by_notify_when: AggregationsTermsAggregateBase; + connector_types_by_consumers: AggregationsTermsAggregateBase; + sum_rules_with_tags: AggregationsSingleMetricAggregateBase; + sum_rules_snoozed: AggregationsSingleMetricAggregateBase; + sum_rules_muted: AggregationsSingleMetricAggregateBase; + sum_rules_with_muted_alerts: AggregationsSingleMetricAggregateBase; }; const totalRulesCount = typeof results.hits.total === 'number' ? results.hits.total : results.hits.total?.value; + const countRulesByExecutionStatus = groupRulesByStatus( + parseSimpleRuleTypeBucket(aggregations.by_execution_status.buckets) + ); + + const countRulesByNotifyWhen = groupRulesByNotifyWhen( + parseSimpleRuleTypeBucket(aggregations.by_notify_when.buckets) + ); + + const countConnectorTypesByConsumers = groupConnectorsByConsumers( + aggregations.connector_types_by_consumers.buckets + ); + return { hasErrors: false, count_total: totalRulesCount ?? 0, count_by_type: parseSimpleRuleTypeBucket(aggregations.by_rule_type_id.buckets), + count_rules_by_execution_status: countRulesByExecutionStatus, + count_rules_with_tags: aggregations.sum_rules_with_tags.value ?? 0, + count_rules_by_notify_when: countRulesByNotifyWhen, + count_rules_snoozed: aggregations.sum_rules_snoozed.value ?? 0, + count_rules_muted: aggregations.sum_rules_muted.value ?? 0, + count_rules_with_muted_alerts: aggregations.sum_rules_with_muted_alerts.value ?? 0, + count_connector_types_by_consumers: countConnectorTypesByConsumers, throttle_time: { min: `${aggregations.min_throttle_time.value ?? 0}s`, avg: `${aggregations.avg_throttle_time.value ?? 0}s`, @@ -232,6 +358,17 @@ export async function getTotalCountAggregations({ errorMessage, count_total: 0, count_by_type: {}, + count_rules_by_execution_status: { success: 0, error: 0, warning: 0 }, + count_rules_by_notify_when: { + on_throttle_interval: 0, + on_active_alert: 0, + on_action_group_change: 0, + }, + count_rules_with_tags: 0, + count_rules_snoozed: 0, + count_rules_muted: 0, + count_rules_with_muted_alerts: 0, + count_connector_types_by_consumers: {}, throttle_time: { min: '0s', avg: '0s', diff --git a/x-pack/plugins/alerting/server/usage/lib/group_connectors_by_consumers.ts b/x-pack/plugins/alerting/server/usage/lib/group_connectors_by_consumers.ts new file mode 100644 index 0000000000000..b189e2868b418 --- /dev/null +++ b/x-pack/plugins/alerting/server/usage/lib/group_connectors_by_consumers.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 { AggregationsBuckets } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { replaceDotSymbols } from './replace_dots_with_underscores'; + +export interface ConnectorsByConsumersBucket { + key: string; + actions: { connector_types: { buckets: Array<{ key: string; doc_count: number }> } }; +} + +export function groupConnectorsByConsumers( + consumers: AggregationsBuckets +) { + return (consumers as ConnectorsByConsumersBucket[]).reduce((acc, consumer) => { + return { + ...acc, + [consumer.key]: consumer.actions.connector_types.buckets.reduce((accBucket, bucket) => { + return { ...accBucket, [replaceDotSymbols(bucket.key)]: bucket.doc_count }; + }, {}), + }; + }, {}); +} diff --git a/x-pack/plugins/alerting/server/usage/lib/group_rules_by_notify_when.test.ts b/x-pack/plugins/alerting/server/usage/lib/group_rules_by_notify_when.test.ts new file mode 100644 index 0000000000000..bacb5114b8ddd --- /dev/null +++ b/x-pack/plugins/alerting/server/usage/lib/group_rules_by_notify_when.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 { groupRulesByNotifyWhen } from './group_rules_by_notify_when'; + +describe('groupRulesByNotifyWhen', () => { + test('should correctly group rules by combining ok and active statuses', () => { + expect( + groupRulesByNotifyWhen({ + onActionGroupChange: 1, + onActiveAlert: 2, + onThrottleInterval: 3, + foo: 5, + }) + ).toEqual({ + on_action_group_change: 1, + on_active_alert: 2, + on_throttle_interval: 3, + }); + }); + + test('should fallback to 0 if any of the expected statuses are absent', () => { + expect(groupRulesByNotifyWhen({ unknown: 100, bar: 300 })).toEqual({ + on_action_group_change: 0, + on_active_alert: 0, + on_throttle_interval: 0, + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/usage/lib/group_rules_by_notify_when.ts b/x-pack/plugins/alerting/server/usage/lib/group_rules_by_notify_when.ts new file mode 100644 index 0000000000000..3adc9c73f9a72 --- /dev/null +++ b/x-pack/plugins/alerting/server/usage/lib/group_rules_by_notify_when.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 { AlertingUsage } from '../types'; + +export function groupRulesByNotifyWhen( + rulesByNotifyWhen: Record +): AlertingUsage['count_rules_by_notify_when'] { + return { + on_action_group_change: rulesByNotifyWhen.onActionGroupChange ?? 0, + on_active_alert: rulesByNotifyWhen.onActiveAlert ?? 0, + on_throttle_interval: rulesByNotifyWhen.onThrottleInterval ?? 0, + }; +} diff --git a/x-pack/plugins/alerting/server/usage/lib/group_rules_by_status.test.ts b/x-pack/plugins/alerting/server/usage/lib/group_rules_by_status.test.ts new file mode 100644 index 0000000000000..f5f24908a3d12 --- /dev/null +++ b/x-pack/plugins/alerting/server/usage/lib/group_rules_by_status.test.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 { groupRulesByStatus } from './group_rules_by_status'; + +describe('groupRulesByStatus', () => { + test('should correctly group rules by combining ok and active statuses', () => { + expect( + groupRulesByStatus({ ok: 3, active: 3, error: 4, warning: 5, unknown: 100, pending: 300 }) + ).toEqual({ + success: 6, + error: 4, + warning: 5, + }); + }); + + test('should fallback to 0 if any of the expected statuses are absent', () => { + expect(groupRulesByStatus({ unknown: 100, pending: 300 })).toEqual({ + success: 0, + error: 0, + warning: 0, + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/usage/lib/group_rules_by_status.ts b/x-pack/plugins/alerting/server/usage/lib/group_rules_by_status.ts new file mode 100644 index 0000000000000..a4b114b0cb85f --- /dev/null +++ b/x-pack/plugins/alerting/server/usage/lib/group_rules_by_status.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 { AlertingUsage } from '../types'; + +export function groupRulesByStatus( + rulesByStatus: Record +): AlertingUsage['count_rules_by_execution_status'] { + const ok = rulesByStatus.ok || 0; + const active = rulesByStatus.active || 0; + + return { + success: ok + active, + error: rulesByStatus.error || 0, + warning: rulesByStatus.warning || 0, + }; +} diff --git a/x-pack/plugins/alerting/server/usage/task.ts b/x-pack/plugins/alerting/server/usage/task.ts index 0bbfab30f0796..4f835f3c4a64f 100644 --- a/x-pack/plugins/alerting/server/usage/task.ts +++ b/x-pack/plugins/alerting/server/usage/task.ts @@ -141,6 +141,16 @@ export function telemetryTaskRunner( count_active_by_type: totalInUse.countByType, count_active_total: totalInUse.countTotal, count_disabled_total: totalCountAggregations.count_total - totalInUse.countTotal, + count_rules_by_execution_status: + totalCountAggregations.count_rules_by_execution_status, + count_rules_with_tags: totalCountAggregations.count_rules_with_tags, + count_rules_by_notify_when: totalCountAggregations.count_rules_by_notify_when, + count_rules_snoozed: totalCountAggregations.count_rules_snoozed, + count_rules_muted: totalCountAggregations.count_rules_muted, + count_rules_with_muted_alerts: + totalCountAggregations.count_rules_with_muted_alerts, + count_connector_types_by_consumers: + totalCountAggregations.count_connector_types_by_consumers, count_rules_namespaces: totalInUse.countNamespaces, count_rules_executions_per_day: dailyExecutionCounts.countTotalRuleExecutions, count_rules_executions_by_type_per_day: diff --git a/x-pack/plugins/alerting/server/usage/types.ts b/x-pack/plugins/alerting/server/usage/types.ts index a0f45d1932309..29a32de29bc1a 100644 --- a/x-pack/plugins/alerting/server/usage/types.ts +++ b/x-pack/plugins/alerting/server/usage/types.ts @@ -27,6 +27,21 @@ export interface AlertingUsage { string, Record >; + count_rules_by_execution_status: { + success: number; + error: number; + warning: number; + }; + count_rules_with_tags: number; + count_rules_by_notify_when: { + on_action_group_change: number; + on_active_alert: number; + on_throttle_interval: number; + }; + count_connector_types_by_consumers: Record>; + count_rules_snoozed: number; + count_rules_muted: number; + count_rules_with_muted_alerts: number; percentile_num_generated_actions_per_day: { p50: number; p90: number; diff --git a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap index fddee59d9c6c1..3ac48a1e367ce 100644 --- a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap @@ -37,8 +37,12 @@ exports[`Error CLOUD_REGION 1`] = `"europe-west1"`; exports[`Error CLOUD_SERVICE_NAME 1`] = `undefined`; +exports[`Error CONTAINER 1`] = `undefined`; + exports[`Error CONTAINER_ID 1`] = `undefined`; +exports[`Error CONTAINER_IMAGE 1`] = `undefined`; + exports[`Error DESTINATION_ADDRESS 1`] = `undefined`; exports[`Error ERROR_CULPRIT 1`] = `"handleOopsie"`; @@ -93,6 +97,24 @@ exports[`Error INDEX 1`] = `undefined`; exports[`Error KUBERNETES 1`] = `undefined`; +exports[`Error KUBERNETES_CONTAINER_NAME 1`] = `undefined`; + +exports[`Error KUBERNETES_DEPLOYMENT 1`] = `undefined`; + +exports[`Error KUBERNETES_DEPLOYMENT_NAME 1`] = `undefined`; + +exports[`Error KUBERNETES_NAMESPACE 1`] = `undefined`; + +exports[`Error KUBERNETES_NAMESPACE_NAME 1`] = `undefined`; + +exports[`Error KUBERNETES_POD_NAME 1`] = `undefined`; + +exports[`Error KUBERNETES_POD_UID 1`] = `undefined`; + +exports[`Error KUBERNETES_REPLICASET 1`] = `undefined`; + +exports[`Error KUBERNETES_REPLICASET_NAME 1`] = `undefined`; + exports[`Error LABEL_NAME 1`] = `undefined`; exports[`Error METRIC_CGROUP_MEMORY_LIMIT_BYTES 1`] = `undefined`; @@ -133,8 +155,6 @@ exports[`Error OBSERVER_LISTENING 1`] = `undefined`; exports[`Error PARENT_ID 1`] = `"parentId"`; -exports[`Error POD_NAME 1`] = `undefined`; - exports[`Error PROCESSOR_EVENT 1`] = `"error"`; exports[`Error SERVICE 1`] = ` @@ -266,8 +286,12 @@ exports[`Span CLOUD_REGION 1`] = `"europe-west1"`; exports[`Span CLOUD_SERVICE_NAME 1`] = `undefined`; +exports[`Span CONTAINER 1`] = `undefined`; + exports[`Span CONTAINER_ID 1`] = `undefined`; +exports[`Span CONTAINER_IMAGE 1`] = `undefined`; + exports[`Span DESTINATION_ADDRESS 1`] = `undefined`; exports[`Span ERROR_CULPRIT 1`] = `undefined`; @@ -318,6 +342,24 @@ exports[`Span INDEX 1`] = `undefined`; exports[`Span KUBERNETES 1`] = `undefined`; +exports[`Span KUBERNETES_CONTAINER_NAME 1`] = `undefined`; + +exports[`Span KUBERNETES_DEPLOYMENT 1`] = `undefined`; + +exports[`Span KUBERNETES_DEPLOYMENT_NAME 1`] = `undefined`; + +exports[`Span KUBERNETES_NAMESPACE 1`] = `undefined`; + +exports[`Span KUBERNETES_NAMESPACE_NAME 1`] = `undefined`; + +exports[`Span KUBERNETES_POD_NAME 1`] = `undefined`; + +exports[`Span KUBERNETES_POD_UID 1`] = `undefined`; + +exports[`Span KUBERNETES_REPLICASET 1`] = `undefined`; + +exports[`Span KUBERNETES_REPLICASET_NAME 1`] = `undefined`; + exports[`Span LABEL_NAME 1`] = `undefined`; exports[`Span METRIC_CGROUP_MEMORY_LIMIT_BYTES 1`] = `undefined`; @@ -358,8 +400,6 @@ exports[`Span OBSERVER_LISTENING 1`] = `undefined`; exports[`Span PARENT_ID 1`] = `"parentId"`; -exports[`Span POD_NAME 1`] = `undefined`; - exports[`Span PROCESSOR_EVENT 1`] = `"span"`; exports[`Span SERVICE 1`] = ` @@ -487,8 +527,16 @@ exports[`Transaction CLOUD_REGION 1`] = `"europe-west1"`; exports[`Transaction CLOUD_SERVICE_NAME 1`] = `undefined`; +exports[`Transaction CONTAINER 1`] = ` +Object { + "id": "container1234567890abcdef", +} +`; + exports[`Transaction CONTAINER_ID 1`] = `"container1234567890abcdef"`; +exports[`Transaction CONTAINER_IMAGE 1`] = `undefined`; + exports[`Transaction DESTINATION_ADDRESS 1`] = `undefined`; exports[`Transaction ERROR_CULPRIT 1`] = `undefined`; @@ -549,6 +597,24 @@ Object { } `; +exports[`Transaction KUBERNETES_CONTAINER_NAME 1`] = `undefined`; + +exports[`Transaction KUBERNETES_DEPLOYMENT 1`] = `undefined`; + +exports[`Transaction KUBERNETES_DEPLOYMENT_NAME 1`] = `undefined`; + +exports[`Transaction KUBERNETES_NAMESPACE 1`] = `undefined`; + +exports[`Transaction KUBERNETES_NAMESPACE_NAME 1`] = `undefined`; + +exports[`Transaction KUBERNETES_POD_NAME 1`] = `undefined`; + +exports[`Transaction KUBERNETES_POD_UID 1`] = `"pod1234567890abcdef"`; + +exports[`Transaction KUBERNETES_REPLICASET 1`] = `undefined`; + +exports[`Transaction KUBERNETES_REPLICASET_NAME 1`] = `undefined`; + exports[`Transaction LABEL_NAME 1`] = `undefined`; exports[`Transaction METRIC_CGROUP_MEMORY_LIMIT_BYTES 1`] = `undefined`; @@ -589,8 +655,6 @@ exports[`Transaction OBSERVER_LISTENING 1`] = `undefined`; exports[`Transaction PARENT_ID 1`] = `"parentId"`; -exports[`Transaction POD_NAME 1`] = `undefined`; - exports[`Transaction PROCESSOR_EVENT 1`] = `"transaction"`; exports[`Transaction SERVICE 1`] = ` diff --git a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts index 1e227713f0dba..575588018b369 100644 --- a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts +++ b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts @@ -121,8 +121,20 @@ export const HOST_HOSTNAME = 'host.hostname'; // Do not use. Please use `HOST_NA export const HOST_NAME = 'host.name'; export const HOST_OS_PLATFORM = 'host.os.platform'; export const CONTAINER_ID = 'container.id'; +export const CONTAINER = 'container'; +export const CONTAINER_IMAGE = 'container.image.name'; + +// Kubernetes export const KUBERNETES = 'kubernetes'; -export const POD_NAME = 'kubernetes.pod.name'; +export const KUBERNETES_CONTAINER_NAME = 'kubernetes.container.name'; +export const KUBERNETES_DEPLOYMENT = 'kubernetes.deployment'; +export const KUBERNETES_DEPLOYMENT_NAME = 'kubernetes.deployment.name'; +export const KUBERNETES_NAMESPACE_NAME = 'kubernetes.namespace.name'; +export const KUBERNETES_NAMESPACE = 'kubernetes.namespace'; +export const KUBERNETES_POD_NAME = 'kubernetes.pod.name'; +export const KUBERNETES_POD_UID = 'kubernetes.pod.uid'; +export const KUBERNETES_REPLICASET = 'kubernetes.replicaset'; +export const KUBERNETES_REPLICASET_NAME = 'kubernetes.replicaset.name'; export const CLIENT_GEO_COUNTRY_ISO_CODE = 'client.geo.country_iso_code'; diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index d8ff2086fd291..95bbeb7b0b19d 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -19,7 +19,8 @@ "triggersActionsUi", "share", "unifiedSearch", - "dataViews" + "dataViews", + "advancedSettings" ], "optionalPlugins": [ "actions", @@ -48,4 +49,4 @@ "observability", "esUiShared" ] -} +} \ No newline at end of file diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/intance_details.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/intance_details.tsx index b61a2cd53cccb..c759f356753a0 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/intance_details.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/intance_details.tsx @@ -18,12 +18,18 @@ import { CLOUD_PROVIDER, CONTAINER_ID, HOST_NAME, - POD_NAME, SERVICE_NODE_NAME, SERVICE_RUNTIME_NAME, SERVICE_RUNTIME_VERSION, SERVICE_VERSION, + KUBERNETES_CONTAINER_NAME, + KUBERNETES_NAMESPACE, + KUBERNETES_POD_NAME, + KUBERNETES_POD_UID, + KUBERNETES_REPLICASET_NAME, + KUBERNETES_DEPLOYMENT_NAME, } from '../../../../../common/elasticsearch_fieldnames'; + import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { useTheme } from '../../../../hooks/use_theme'; import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; @@ -42,8 +48,20 @@ interface Props { kuery: string; } -function toKeyValuePairs(keys: string[], data: ServiceInstanceDetails) { - return keys.map((key) => ({ key, value: get(data, key) })); +function toKeyValuePairs({ + keys, + data, + isFilterable = true, +}: { + keys: string[]; + data: ServiceInstanceDetails; + isFilterable?: boolean; +}) { + return keys.map((key) => ({ + key, + value: get(data, key), + isFilterable, + })); } const serviceDetailsKeys = [ @@ -52,7 +70,18 @@ const serviceDetailsKeys = [ SERVICE_RUNTIME_NAME, SERVICE_RUNTIME_VERSION, ]; -const containerDetailsKeys = [CONTAINER_ID, HOST_NAME, POD_NAME]; +const containerDetailsKeys = [ + CONTAINER_ID, + HOST_NAME, + KUBERNETES_POD_UID, + KUBERNETES_POD_NAME, +]; +const metricsKubernetesDetailsKeys = [ + KUBERNETES_CONTAINER_NAME, + KUBERNETES_NAMESPACE, + KUBERNETES_REPLICASET_NAME, + KUBERNETES_DEPLOYMENT_NAME, +]; const cloudDetailsKeys = [ CLOUD_AVAILABILITY_ZONE, CLOUD_INSTANCE_ID, @@ -93,12 +122,23 @@ export function InstanceDetails({ pushNewItemToKueryBar({ kuery, history, key, value }); }; - const serviceDetailsKeyValuePairs = toKeyValuePairs(serviceDetailsKeys, data); - const containerDetailsKeyValuePairs = toKeyValuePairs( - containerDetailsKeys, - data - ); - const cloudDetailsKeyValuePairs = toKeyValuePairs(cloudDetailsKeys, data); + const serviceDetailsKeyValuePairs = toKeyValuePairs({ + keys: serviceDetailsKeys, + data, + }); + const containerDetailsKeyValuePairs = toKeyValuePairs({ + keys: containerDetailsKeys, + data, + }); + const metricsKubernetesKeyValuePairs = toKeyValuePairs({ + keys: metricsKubernetesDetailsKeys, + data, + isFilterable: false, + }); + const cloudDetailsKeyValuePairs = toKeyValuePairs({ + keys: cloudDetailsKeys, + data, + }); const containerType = data.kubernetes?.pod?.name ? 'Kubernetes' : 'Docker'; return ( @@ -122,7 +162,10 @@ export function InstanceDetails({ { defaultMessage: 'Container' } )} icon={getContainerIcon(containerType)} - keyValueList={containerDetailsKeyValuePairs} + keyValueList={[ + ...containerDetailsKeyValuePairs, + ...metricsKubernetesKeyValuePairs, + ]} onClickFilter={addKueryBarFilter} /> diff --git a/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx b/x-pack/plugins/apm/public/components/app/settings/general_settings/index.tsx new file mode 100644 index 0000000000000..ef41b75aedeaf --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/settings/general_settings/index.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 { EuiButton } from '@elastic/eui'; +import { LazyField } from '@kbn/advanced-settings-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { + apmLabsButton, + apmProgressiveLoading, + apmServiceGroupMaxNumberOfServices, + defaultApmServiceEnvironment, + enableComparisonByDefault, + enableInspectEsQueries, +} from '@kbn/observability-plugin/common'; +import { isEmpty } from 'lodash'; +import React from 'react'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useApmEditableSettings } from '../../../../hooks/use_apm_editable_settings'; + +const apmSettingsKeys = [ + enableComparisonByDefault, + defaultApmServiceEnvironment, + apmProgressiveLoading, + apmServiceGroupMaxNumberOfServices, + enableInspectEsQueries, + apmLabsButton, +]; + +export function GeneralSettings() { + const { docLinks, notifications } = useApmPluginContext().core; + const { + handleFieldChange, + settingsEditableConfig, + unsavedChanges, + saveAll, + isSaving, + } = useApmEditableSettings(apmSettingsKeys); + + async function handleSave() { + try { + const reloadPage = Object.keys(unsavedChanges).some((key) => { + return settingsEditableConfig[key].requiresPageReload; + }); + await saveAll(); + if (reloadPage) { + window.location.reload(); + } + } catch (e) { + const error = e as Error; + notifications.toasts.addDanger({ + title: i18n.translate('xpack.apm.apmSettings.save.error', { + defaultMessage: 'An error occurred while saving the settings', + }), + text: error.message, + }); + } + } + + return ( + <> + {apmSettingsKeys.map((settingKey) => { + const editableConfig = settingsEditableConfig[settingKey]; + return ( + + ); + })} + + {i18n.translate('xpack.apm.labs.reload', { + defaultMessage: 'Reload to apply changes', + })} + + + ); +} diff --git a/x-pack/plugins/apm/public/components/routing/settings/index.tsx b/x-pack/plugins/apm/public/components/routing/settings/index.tsx index ac93c31581e61..161afd2706b68 100644 --- a/x-pack/plugins/apm/public/components/routing/settings/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/settings/index.tsx @@ -20,6 +20,7 @@ import { CustomLinkOverview } from '../../app/settings/custom_link'; import { Schema } from '../../app/settings/schema'; import { AnomalyDetection } from '../../app/settings/anomaly_detection'; import { AgentKeys } from '../../app/settings/agent_keys'; +import { GeneralSettings } from '../../app/settings/general_settings'; function page({ title, @@ -54,6 +55,14 @@ export const settings = { ), children: { + '/settings/general-settings': page({ + title: i18n.translate( + 'xpack.apm.views.settings.generalSettings.title', + { defaultMessage: 'General settings' } + ), + element: , + tab: 'general-settings', + }), '/settings/agent-configuration': page({ tab: 'agent-configuration', title: i18n.translate( @@ -133,7 +142,7 @@ export const settings = { tab: 'agent-keys', }), '/settings': { - element: , + element: , }, }, }, diff --git a/x-pack/plugins/apm/public/components/routing/templates/settings_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/settings_template.tsx index aaec73e11ed68..04145002cc1b8 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/settings_template.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/settings_template.tsx @@ -8,12 +8,11 @@ import { EuiPageHeaderProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { History } from 'history'; -import { useHistory } from 'react-router-dom'; import { CoreStart } from '@kbn/core/public'; import { ApmMainTemplate } from './apm_main_template'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; -import { getLegacyApmHref } from '../../shared/links/apm/apm_link'; +import { useApmRouter } from '../../../hooks/use_apm_router'; +import { ApmRouter } from '../apm_route_config'; type Tab = NonNullable[0] & { key: @@ -22,7 +21,8 @@ type Tab = NonNullable[0] & { | 'anomaly-detection' | 'apm-indices' | 'custom-links' - | 'schema'; + | 'schema' + | 'general-settings'; hidden?: boolean; }; @@ -33,8 +33,8 @@ interface Props { export function SettingsTemplate({ children, selectedTab }: Props) { const { core } = useApmPluginContext(); - const history = useHistory(); - const tabs = getTabs({ history, core, selectedTab }); + const router = useApmRouter(); + const tabs = getTabs({ core, selectedTab, router }); return ( ( + apmLabsButton, + false + ); + return ( + {isLabsButtonEnabled && } !state); + } + + return ( + <> + + {i18n.translate('xpack.apm.labs', { defaultMessage: 'Labs' })} + + {isOpen && } + + ); +} diff --git a/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/labs/labs_flyout.tsx b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/labs/labs_flyout.tsx new file mode 100644 index 0000000000000..408070ab88900 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/apm_header_action_menu/labs/labs_flyout.tsx @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiHorizontalRule, + EuiIcon, + EuiLoadingContent, + EuiTitle, +} from '@elastic/eui'; +import { LazyField } from '@kbn/advanced-settings-plugin/public'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useApmEditableSettings } from '../../../../hooks/use_apm_editable_settings'; +import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; + +interface Props { + onClose: () => void; +} + +export function LabsFlyout({ onClose }: Props) { + const { docLinks, notifications } = useApmPluginContext().core; + + const { data, status } = useFetcher( + (callApmApi) => callApmApi('GET /internal/apm/settings/labs'), + [] + ); + const labsItems = data?.labsItems || []; + + const { + handleFieldChange, + settingsEditableConfig, + unsavedChanges, + saveAll, + isSaving, + cleanUnsavedChanges, + } = useApmEditableSettings(labsItems); + + async function handleSave() { + try { + const reloadPage = Object.keys(unsavedChanges).some((key) => { + return settingsEditableConfig[key].requiresPageReload; + }); + + await saveAll(); + + if (reloadPage) { + window.location.reload(); + } else { + onClose(); + } + } catch (e) { + const error = e as Error; + notifications.toasts.addDanger({ + title: i18n.translate('xpack.apm.apmSettings.save.error', { + defaultMessage: 'An error occurred while saving the settings', + }), + text: error.message, + }); + } + } + + function handelCancel() { + cleanUnsavedChanges(); + onClose(); + } + + const isLoading = + status === FETCH_STATUS.NOT_INITIATED || status === FETCH_STATUS.LOADING; + + return ( + + + + + + + + +

+ {i18n.translate('xpack.apm.labs', { + defaultMessage: 'Labs', + })} +

+
+
+
+
+ + {isLoading ? ( + + ) : ( + <> + + {labsItems.map((settingKey, i) => { + const editableConfig = settingsEditableConfig[settingKey]; + return ( + <> + + + + ); + })} + + + + + + {i18n.translate('xpack.apm.labs.cancel', { + defaultMessage: 'Cancel', + })} + + + + + {i18n.translate('xpack.apm.labs.reload', { + defaultMessage: 'Reload to apply changes', + })} + + + + + + )} +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/shared/key_value_filter_list/index.tsx b/x-pack/plugins/apm/public/components/shared/key_value_filter_list/index.tsx index c1fb78546605f..20ab03e42025b 100644 --- a/x-pack/plugins/apm/public/components/shared/key_value_filter_list/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/key_value_filter_list/index.tsx @@ -19,10 +19,12 @@ import { import { i18n } from '@kbn/i18n'; import React, { Fragment } from 'react'; import styled from 'styled-components'; +import { isEmpty } from 'lodash'; interface KeyValue { key: string; value: any | undefined; + isFilterable: boolean; } const StyledEuiAccordion = styled(EuiAccordion)` @@ -43,13 +45,8 @@ const StyledEuiDescriptionList = styled(EuiDescriptionList)` display: flex; `; -const ValueContainer = styled.div` - display: flex; - align-items: center; -`; - function removeEmptyValues(items: KeyValue[]) { - return items.filter(({ value }) => value !== undefined); + return items.filter(({ value }) => !isEmpty(value)); } export function KeyValueFilterList({ @@ -78,12 +75,12 @@ export function KeyValueFilterList({ buttonClassName="buttonContentContainer" > - {nonEmptyKeyValueList.map(({ key, value }) => { + {nonEmptyKeyValueList.map(({ key, value, isFilterable }) => { return ( {key} @@ -91,27 +88,37 @@ export function KeyValueFilterList({ - - { - onClickFilter({ key, value }); - }} - data-test-subj={`filter_by_${key}`} - > - - - - - {value} - + + + {isFilterable && ( + { + onClickFilter({ key, value }); + }} + data-test-subj={`filter_by_${key}`} + > + + + + + )} + + + {value} + + ); diff --git a/x-pack/plugins/apm/public/components/shared/key_value_filter_list/key_value_filter_list.test.tsx b/x-pack/plugins/apm/public/components/shared/key_value_filter_list/key_value_filter_list.test.tsx index 8270c8aa64d41..c06bc9d86afd0 100644 --- a/x-pack/plugins/apm/public/components/shared/key_value_filter_list/key_value_filter_list.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/key_value_filter_list/key_value_filter_list.test.tsx @@ -28,8 +28,8 @@ describe('KeyValueFilterList', () => { @@ -48,8 +48,8 @@ describe('KeyValueFilterList', () => { title="title" icon="alert" keyValueList={[ - { key: 'foo', value: 'foo value' }, - { key: 'bar', value: 'bar value' }, + { key: 'foo', value: 'foo value', isFilterable: true }, + { key: 'bar', value: 'bar value', isFilterable: true }, ]} onClickFilter={jest.fn} /> @@ -62,8 +62,8 @@ describe('KeyValueFilterList', () => { @@ -71,20 +71,38 @@ describe('KeyValueFilterList', () => { expect(component.queryAllByTestId('accordion_title_icon')).toEqual([]); expectTextsInDocument(component, ['title']); }); + + it('hides filter by value option', () => { + const component = renderWithTheme( + + ); + expect(component.queryAllByTestId('filter_by_foo')).toEqual([]); + expect(component.queryAllByTestId('filter_by_bar')).toHaveLength(1); + }); it('returns selected key value when the filter button is clicked', () => { const mockFilter = jest.fn(); const component = renderWithTheme( ); fireEvent.click(component.getByTestId('filter_by_foo')); - expect(mockFilter).toHaveBeenCalledWith({ key: 'foo', value: 'foo value' }); + expect(mockFilter).toHaveBeenCalledWith({ + key: 'foo', + value: 'foo value', + }); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/service_icons/container_details.tsx b/x-pack/plugins/apm/public/components/shared/service_icons/container_details.tsx index aff1a6d9c053f..ea029a90018b1 100644 --- a/x-pack/plugins/apm/public/components/shared/service_icons/container_details.tsx +++ b/x-pack/plugins/apm/public/components/shared/service_icons/container_details.tsx @@ -5,7 +5,11 @@ * 2.0. */ -import { EuiDescriptionList, EuiDescriptionListProps } from '@elastic/eui'; +import { + EuiDescriptionList, + EuiDescriptionListProps, + EuiBadge, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { asInteger } from '../../../../common/utils/formatters'; @@ -16,18 +20,38 @@ type ServiceDetailsReturnType = interface Props { container: ServiceDetailsReturnType['container']; + kubernetes: ServiceDetailsReturnType['kubernetes']; } -export function ContainerDetails({ container }: Props) { +export function ContainerDetails({ container, kubernetes }: Props) { if (!container) { return null; } const listItems: EuiDescriptionListProps['listItems'] = []; + + if (kubernetes?.containerImages && kubernetes?.containerImages.length > 0) { + listItems.push({ + title: i18n.translate( + 'xpack.apm.serviceIcons.serviceDetails.container.image.name', + { defaultMessage: 'Container images' } + ), + description: ( +
    + {kubernetes.containerImages.map((deployment, index) => ( +
  • + {deployment} +
  • + ))} +
+ ), + }); + } + if (container.os) { listItems.push({ title: i18n.translate( - 'xpack.apm.serviceIcons.serviceDetails.container.osLabel', + 'xpack.apm.serviceIcons.serviceDetails.container.os.label', { defaultMessage: 'OS', } @@ -36,45 +60,67 @@ export function ContainerDetails({ container }: Props) { }); } - if (container.isContainerized !== undefined) { + if (kubernetes?.deployments && kubernetes?.deployments.length > 0) { listItems.push({ title: i18n.translate( - 'xpack.apm.serviceIcons.serviceDetails.container.containerizedLabel', - { defaultMessage: 'Containerized' } + 'xpack.apm.serviceIcons.serviceDetails.kubernetes.deployments', + { defaultMessage: 'Deployments' } + ), + description: ( +
    + {kubernetes.deployments.map((deployment, index) => ( +
  • + {deployment} +
  • + ))} +
), - description: container.isContainerized - ? i18n.translate( - 'xpack.apm.serviceIcons.serviceDetails.container.yesLabel', - { - defaultMessage: 'Yes', - } - ) - : i18n.translate( - 'xpack.apm.serviceIcons.serviceDetails.container.noLabel', - { - defaultMessage: 'No', - } - ), }); } - if (container.totalNumberInstances) { + if (kubernetes?.namespaces && kubernetes?.namespaces.length > 0) { listItems.push({ title: i18n.translate( - 'xpack.apm.serviceIcons.serviceDetails.container.totalNumberInstancesLabel', - { defaultMessage: 'Total number of instances' } + 'xpack.apm.serviceIcons.serviceDetails.kubernetes.namespaces', + { defaultMessage: 'Namespaces' } + ), + description: ( +
    + {kubernetes.namespaces.map((namespace, index) => ( +
  • + {namespace} +
  • + ))} +
+ ), + }); + } + + if (kubernetes?.replicasets && kubernetes?.replicasets.length > 0) { + listItems.push({ + title: i18n.translate( + 'xpack.apm.serviceIcons.serviceDetails.kubernetes.replicasets', + { defaultMessage: 'Replicasets' } + ), + description: ( +
    + {kubernetes.replicasets.map((replicaset, index) => ( +
  • + {replicaset} +
  • + ))} +
), - description: asInteger(container.totalNumberInstances), }); } - if (container.type) { + if (container.totalNumberInstances) { listItems.push({ title: i18n.translate( - 'xpack.apm.serviceIcons.serviceDetails.container.orchestrationLabel', - { defaultMessage: 'Orchestration' } + 'xpack.apm.serviceIcons.serviceDetails.container.totalNumberInstancesLabel', + { defaultMessage: 'Total number of instances' } ), - description: container.type, + description: asInteger(container.totalNumberInstances), }); } diff --git a/x-pack/plugins/apm/public/components/shared/service_icons/index.tsx b/x-pack/plugins/apm/public/components/shared/service_icons/index.tsx index 2fd578f10b110..54040f9a57d5e 100644 --- a/x-pack/plugins/apm/public/components/shared/service_icons/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/service_icons/index.tsx @@ -129,7 +129,12 @@ export function ServiceIcons({ start, end, serviceName }: Props) { title: i18n.translate('xpack.apm.serviceIcons.container', { defaultMessage: 'Container', }), - component: , + component: ( + + ), }, { key: 'serverless', diff --git a/x-pack/plugins/apm/public/components/shared/service_icons/service_icons.stories.tsx b/x-pack/plugins/apm/public/components/shared/service_icons/service_icons.stories.tsx index 60757bc0b99be..d5ea675338856 100644 --- a/x-pack/plugins/apm/public/components/shared/service_icons/service_icons.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/service_icons/service_icons.stories.tsx @@ -115,10 +115,13 @@ Example.args = { }, }, container: { - os: 'Linux', - type: 'Kubernetes', - isContainerized: true, totalNumberInstances: 1, + image: 'container image name', + }, + kubernetes: { + deployments: ['opbeans-java', 'opbeans-go-nsn'], + replicasets: ['opbeans-go-6dff977956', 'opbeans-go-nsn-864bdcbc5b'], + namespaces: ['default'], }, cloud: { provider: 'gcp', diff --git a/x-pack/plugins/apm/public/hooks/use_apm_editable_settings.tsx b/x-pack/plugins/apm/public/hooks/use_apm_editable_settings.tsx new file mode 100644 index 0000000000000..05f5edeaec505 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_apm_editable_settings.tsx @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useMemo, useState } from 'react'; +import { FieldState } from '@kbn/advanced-settings-plugin/public'; +import { toEditableConfig } from '@kbn/advanced-settings-plugin/public'; +import { IUiSettingsClient } from '@kbn/core/public'; +import { isEmpty } from 'lodash'; + +function getEditableConfig({ + settingsKeys, + uiSettings, +}: { + settingsKeys: string[]; + uiSettings?: IUiSettingsClient; +}) { + if (!uiSettings) { + return {}; + } + const uiSettingsDefinition = uiSettings.getAll(); + const config: Record> = {}; + + settingsKeys.forEach((key) => { + const settingDef = uiSettingsDefinition?.[key]; + if (settingDef) { + const editableConfig = toEditableConfig({ + def: settingDef, + name: key, + value: settingDef.userValue, + isCustom: uiSettings.isCustom(key), + isOverridden: uiSettings.isOverridden(key), + }); + config[key] = editableConfig; + } + }); + return config; +} + +export function useApmEditableSettings(settingsKeys: string[]) { + const { services } = useKibana(); + const { uiSettings } = services; + const [isSaving, setIsSaving] = useState(false); + const [forceReloadSettings, setForceReloadSettings] = useState(0); + const [unsavedChanges, setUnsavedChanges] = useState< + Record + >({}); + + const settingsEditableConfig = useMemo( + () => { + return getEditableConfig({ settingsKeys, uiSettings }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [uiSettings, settingsKeys, forceReloadSettings] + ); + + function handleFieldChange(key: string, fieldState: FieldState) { + setUnsavedChanges((state) => { + const newState = { ...state }; + const { value, defVal } = settingsEditableConfig[key]; + const currentValue = value === undefined ? defVal : value; + if (currentValue === fieldState.value) { + // Delete property from unsaved object if user changes it to the value that was already saved + delete newState[key]; + } else { + newState[key] = fieldState; + } + return newState; + }); + } + + function cleanUnsavedChanges() { + setUnsavedChanges({}); + } + + async function saveAll() { + if (uiSettings && !isEmpty(unsavedChanges)) { + try { + setIsSaving(true); + const arr = Object.entries(unsavedChanges).map(([key, fieldState]) => + uiSettings.set(key, fieldState.value) + ); + + await Promise.all(arr); + setForceReloadSettings((state) => ++state); + cleanUnsavedChanges(); + } finally { + setIsSaving(false); + } + } + } + + return { + settingsEditableConfig, + unsavedChanges, + handleFieldChange, + saveAll, + isSaving, + cleanUnsavedChanges, + }; +} diff --git a/x-pack/plugins/apm/scripts/aggregate_latency_metrics/index.ts b/x-pack/plugins/apm/scripts/aggregate_latency_metrics/index.ts index a48da0f4577cc..8576034d167ad 100644 --- a/x-pack/plugins/apm/scripts/aggregate_latency_metrics/index.ts +++ b/x-pack/plugins/apm/scripts/aggregate_latency_metrics/index.ts @@ -18,7 +18,7 @@ import { TRANSACTION_TYPE, AGENT_NAME, SERVICE_ENVIRONMENT, - POD_NAME, + KUBERNETES_POD_NAME, CONTAINER_ID, SERVICE_VERSION, TRANSACTION_RESULT, @@ -91,7 +91,7 @@ export async function aggregateLatencyMetrics() { SERVICE_ENVIRONMENT, AGENT_NAME, HOST_NAME, - POD_NAME, + KUBERNETES_POD_NAME, CONTAINER_ID, TRANSACTION_NAME, TRANSACTION_RESULT, diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts index f315c0a1ce521..167fb0d57208f 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts @@ -30,7 +30,7 @@ import { HOST_OS_PLATFORM, OBSERVER_HOSTNAME, PARENT_ID, - POD_NAME, + KUBERNETES_POD_NAME, PROCESSOR_EVENT, SERVICE_ENVIRONMENT, SERVICE_FRAMEWORK_NAME, @@ -180,7 +180,7 @@ export const tasks: TelemetryTask[] = [ SERVICE_VERSION, HOST_NAME, CONTAINER_ID, - POD_NAME, + KUBERNETES_POD_NAME, ].map((field) => ({ terms: { field, missing_bucket: true } })); const observerHostname = { @@ -1206,7 +1206,7 @@ export const tasks: TelemetryTask[] = [ field: SERVICE_RUNTIME_VERSION, }, { - field: POD_NAME, + field: KUBERNETES_POD_NAME, }, { field: CONTAINER_ID, @@ -1314,7 +1314,7 @@ export const tasks: TelemetryTask[] = [ kubernetes: { pod: { name: serviceBucket.top_metrics?.top[0].metrics[ - POD_NAME + KUBERNETES_POD_NAME ] as string, }, }, diff --git a/x-pack/plugins/apm/server/lib/helpers/get_metric_indices.ts b/x-pack/plugins/apm/server/lib/helpers/get_infra_metric_indices.ts similarity index 79% rename from x-pack/plugins/apm/server/lib/helpers/get_metric_indices.ts rename to x-pack/plugins/apm/server/lib/helpers/get_infra_metric_indices.ts index 61d12ba730942..aa978b1643a86 100644 --- a/x-pack/plugins/apm/server/lib/helpers/get_metric_indices.ts +++ b/x-pack/plugins/apm/server/lib/helpers/get_infra_metric_indices.ts @@ -8,7 +8,7 @@ import { SavedObjectsClientContract } from '@kbn/core/server'; import { APMRouteHandlerResources } from '../../routes/typings'; -export async function getMetricIndices({ +export async function getInfraMetricIndices({ infraPlugin, savedObjectsClient, }: { @@ -16,7 +16,7 @@ export async function getMetricIndices({ savedObjectsClient: SavedObjectsClientContract; }): Promise { const infra = await infraPlugin.start(); - const metricIndices = await infra.getMetricIndices(savedObjectsClient); + const infraMetricIndices = await infra.getMetricIndices(savedObjectsClient); - return metricIndices; + return infraMetricIndices; } diff --git a/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts b/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts index 273c3952c080c..0a6beb7f992d6 100644 --- a/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts +++ b/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts @@ -41,6 +41,7 @@ import { timeRangeMetadataRoute } from '../time_range_metadata/route'; import { traceRouteRepository } from '../traces/route'; import { transactionRouteRepository } from '../transactions/route'; import { storageExplorerRouteRepository } from '../storage_explorer/route'; +import { labsRouteRepository } from '../settings/labs/route'; function getTypedGlobalApmServerRouteRepository() { const repository = { @@ -75,6 +76,7 @@ function getTypedGlobalApmServerRouteRepository() { ...infrastructureRouteRepository, ...debugTelemetryRoute, ...timeRangeMetadataRoute, + ...labsRouteRepository, }; return repository; diff --git a/x-pack/plugins/apm/server/routes/infrastructure/get_host_names.ts b/x-pack/plugins/apm/server/routes/infrastructure/get_host_names.ts index 6659525237932..4890f8ab909eb 100644 --- a/x-pack/plugins/apm/server/routes/infrastructure/get_host_names.ts +++ b/x-pack/plugins/apm/server/routes/infrastructure/get_host_names.ts @@ -14,7 +14,7 @@ import { HOST_NAME, } from '../../../common/elasticsearch_fieldnames'; import { ApmPluginRequestHandlerContext } from '../typings'; -import { getMetricIndices } from '../../lib/helpers/get_metric_indices'; +import { getInfraMetricIndices } from '../../lib/helpers/get_infra_metric_indices'; interface Aggs extends estypes.AggregationsMultiBucketAggregateBase { buckets: Array<{ @@ -92,7 +92,7 @@ export const getContainerHostNames = async ({ if (containerIds.length) { const esClient = (await context.core).elasticsearch.client.asCurrentUser; const savedObjectsClient = (await context.core).savedObjects.client; - const metricIndices = await getMetricIndices({ + const metricIndices = await getInfraMetricIndices({ infraPlugin: infra, savedObjectsClient, }); diff --git a/x-pack/plugins/apm/server/routes/infrastructure/get_infrastructure_data.ts b/x-pack/plugins/apm/server/routes/infrastructure/get_infrastructure_data.ts index b1bf617ac7220..027631e15466d 100644 --- a/x-pack/plugins/apm/server/routes/infrastructure/get_infrastructure_data.ts +++ b/x-pack/plugins/apm/server/routes/infrastructure/get_infrastructure_data.ts @@ -13,7 +13,7 @@ import { SERVICE_NAME, CONTAINER_ID, HOST_HOSTNAME, - POD_NAME, + KUBERNETES_POD_NAME, } from '../../../common/elasticsearch_fieldnames'; export const getInfrastructureData = async ({ @@ -64,7 +64,7 @@ export const getInfrastructureData = async ({ }, podNames: { terms: { - field: POD_NAME, + field: KUBERNETES_POD_NAME, size: 500, }, }, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_instance_container_metadata.ts b/x-pack/plugins/apm/server/routes/services/get_service_instance_container_metadata.ts new file mode 100644 index 0000000000000..729c2f1f1b1eb --- /dev/null +++ b/x-pack/plugins/apm/server/routes/services/get_service_instance_container_metadata.ts @@ -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 { ElasticsearchClient } from '@kbn/core/server'; +import { rangeQuery } from '@kbn/observability-plugin/server'; +import { + CONTAINER, + CONTAINER_ID, + CONTAINER_IMAGE, + KUBERNETES, + KUBERNETES_CONTAINER_NAME, + KUBERNETES_NAMESPACE, + KUBERNETES_POD_NAME, + KUBERNETES_POD_UID, + KUBERNETES_REPLICASET_NAME, + KUBERNETES_DEPLOYMENT_NAME, +} from '../../../common/elasticsearch_fieldnames'; +import { Kubernetes } from '../../../typings/es_schemas/raw/fields/kubernetes'; +import { maybe } from '../../../common/utils/maybe'; + +type ServiceInstanceContainerMetadataDetails = + | { + kubernetes: Kubernetes; + } + | undefined; + +export const getServiceInstanceContainerMetadata = async ({ + esClient, + indexName, + containerId, + start, + end, +}: { + esClient: ElasticsearchClient; + indexName?: string; + containerId: string; + start: number; + end: number; +}): Promise => { + if (!indexName) { + return undefined; + } + + const should = [ + { exists: { field: KUBERNETES } }, + { exists: { field: CONTAINER_IMAGE } }, + { exists: { field: KUBERNETES_CONTAINER_NAME } }, + { exists: { field: KUBERNETES_NAMESPACE } }, + { exists: { field: KUBERNETES_POD_NAME } }, + { exists: { field: KUBERNETES_POD_UID } }, + { exists: { field: KUBERNETES_REPLICASET_NAME } }, + { exists: { field: KUBERNETES_DEPLOYMENT_NAME } }, + ]; + + const response = await esClient.search({ + index: [indexName], + _source: [KUBERNETES, CONTAINER], + size: 1, + query: { + bool: { + filter: [ + { + term: { + [CONTAINER_ID]: containerId, + }, + }, + ...rangeQuery(start, end), + ], + should, + }, + }, + }); + + const sample = maybe(response.hits.hits[0]) + ?._source as ServiceInstanceContainerMetadataDetails; + + return { + kubernetes: { + pod: { + name: sample?.kubernetes?.pod?.name, + uid: sample?.kubernetes?.pod?.uid, + }, + deployment: { + name: sample?.kubernetes?.deployment?.name, + }, + replicaset: { + name: sample?.kubernetes?.replicaset?.name, + }, + namespace: sample?.kubernetes?.namespace, + container: { + name: sample?.kubernetes?.container?.name, + id: sample?.kubernetes?.container?.id, + }, + }, + }; +}; diff --git a/x-pack/plugins/apm/server/routes/services/get_service_metadata_details.ts b/x-pack/plugins/apm/server/routes/services/get_service_metadata_details.ts index 588709e46e210..5616c7b5be792 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_metadata_details.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_metadata_details.ts @@ -9,6 +9,7 @@ import { rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { AGENT, + CONTAINER, CLOUD, CLOUD_AVAILABILITY_ZONE, CLOUD_REGION, @@ -49,10 +50,10 @@ export interface ServiceMetadataDetails { }; }; container?: { + ids?: string[]; + image?: string; os?: string; - isContainerized?: boolean; totalNumberInstances?: number; - type?: ContainerType; }; serverless?: { type?: string; @@ -67,6 +68,12 @@ export interface ServiceMetadataDetails { projectName?: string; serviceName?: string; }; + kubernetes?: { + deployments?: string[]; + namespaces?: string[]; + replicasets?: string[]; + containerImages?: string[]; + }; } export async function getServiceMetadataDetails({ @@ -99,7 +106,7 @@ export async function getServiceMetadataDetails({ }, body: { size: 1, - _source: [SERVICE, AGENT, HOST, CONTAINER_ID, KUBERNETES, CLOUD], + _source: [SERVICE, AGENT, HOST, CONTAINER, KUBERNETES, CLOUD], query: { bool: { filter, should } }, aggs: { serviceVersions: { @@ -115,6 +122,12 @@ export async function getServiceMetadataDetails({ size: 10, }, }, + containerIds: { + terms: { + field: CONTAINER_ID, + size: 10, + }, + }, regions: { terms: { field: CLOUD_REGION, @@ -181,10 +194,12 @@ export async function getServiceMetadataDetails({ const containerDetails = host || container || totalNumberInstances || kubernetes ? { - os: host?.os?.platform, type: (!!kubernetes ? 'Kubernetes' : 'Docker') as ContainerType, - isContainerized: !!container?.id, + os: host?.os?.platform, totalNumberInstances, + ids: response.aggregations?.containerIds.buckets.map( + (bucket) => bucket.key as string + ), } : undefined; diff --git a/x-pack/plugins/apm/server/routes/services/get_service_metadata_icons.ts b/x-pack/plugins/apm/server/routes/services/get_service_metadata_icons.ts index a099b9821f961..68c4fa0b46127 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_metadata_icons.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_metadata_icons.ts @@ -14,7 +14,7 @@ import { CONTAINER_ID, KUBERNETES, SERVICE_NAME, - POD_NAME, + KUBERNETES_POD_NAME, HOST_OS_PLATFORM, } from '../../../common/elasticsearch_fieldnames'; import { ContainerType } from '../../../common/service_metadata'; @@ -36,7 +36,7 @@ export interface ServiceMetadataIcons { export const should = [ { exists: { field: CONTAINER_ID } }, - { exists: { field: POD_NAME } }, + { exists: { field: KUBERNETES_POD_NAME } }, { exists: { field: CLOUD_PROVIDER } }, { exists: { field: HOST_OS_PLATFORM } }, { exists: { field: AGENT_NAME } }, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts b/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts new file mode 100644 index 0000000000000..5b267e4faa3fc --- /dev/null +++ b/x-pack/plugins/apm/server/routes/services/get_service_overview_container_metadata.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import { rangeQuery } from '@kbn/observability-plugin/server'; +import { + CONTAINER, + CONTAINER_ID, + CONTAINER_IMAGE, + KUBERNETES, + KUBERNETES_CONTAINER_NAME, + KUBERNETES_NAMESPACE, + KUBERNETES_POD_NAME, + KUBERNETES_POD_UID, + KUBERNETES_REPLICASET_NAME, + KUBERNETES_DEPLOYMENT_NAME, +} from '../../../common/elasticsearch_fieldnames'; + +type ServiceOverviewContainerMetadataDetails = + | { + kubernetes: { + deployments?: string[]; + replicasets?: string[]; + namespaces?: string[]; + containerImages?: string[]; + }; + } + | undefined; + +interface ResponseAggregations { + [key: string]: { + buckets: Array<{ + key: string; + }>; + }; +} + +export const getServiceOverviewContainerMetadata = async ({ + esClient, + indexName, + containerIds, + start, + end, +}: { + esClient: ElasticsearchClient; + indexName?: string; + containerIds: string[]; + start: number; + end: number; +}): Promise => { + if (!indexName) { + return undefined; + } + + const should = [ + { exists: { field: KUBERNETES } }, + { exists: { field: CONTAINER_IMAGE } }, + { exists: { field: KUBERNETES_CONTAINER_NAME } }, + { exists: { field: KUBERNETES_NAMESPACE } }, + { exists: { field: KUBERNETES_POD_NAME } }, + { exists: { field: KUBERNETES_POD_UID } }, + { exists: { field: KUBERNETES_REPLICASET_NAME } }, + { exists: { field: KUBERNETES_DEPLOYMENT_NAME } }, + ]; + + const response = await esClient.search({ + index: [indexName], + _source: [KUBERNETES, CONTAINER], + size: 0, + query: { + bool: { + filter: [ + { + terms: { + [CONTAINER_ID]: containerIds, + }, + }, + ...rangeQuery(start, end), + ], + should, + }, + }, + aggs: { + deployments: { + terms: { + field: KUBERNETES_DEPLOYMENT_NAME, + size: 10, + }, + }, + namespaces: { + terms: { + field: KUBERNETES_NAMESPACE, + size: 10, + }, + }, + replicasets: { + terms: { + field: KUBERNETES_REPLICASET_NAME, + size: 10, + }, + }, + containerImages: { + terms: { + field: CONTAINER_IMAGE, + size: 10, + }, + }, + }, + }); + + return { + kubernetes: { + deployments: response.aggregations?.deployments?.buckets.map( + (bucket) => bucket.key + ), + replicasets: response.aggregations?.replicasets?.buckets.map( + (bucket) => bucket.key + ), + namespaces: response.aggregations?.namespaces?.buckets.map( + (bucket) => bucket.key + ), + containerImages: response.aggregations?.containerImages?.buckets.map( + (bucket) => bucket.key + ), + }, + }; +}; diff --git a/x-pack/plugins/apm/server/routes/services/route.ts b/x-pack/plugins/apm/server/routes/services/route.ts index 26557e2adfb44..4b76e53877d38 100644 --- a/x-pack/plugins/apm/server/routes/services/route.ts +++ b/x-pack/plugins/apm/server/routes/services/route.ts @@ -8,7 +8,7 @@ import Boom from '@hapi/boom'; import { isoToEpochRt, jsonRt, toNumberRt } from '@kbn/io-ts-utils'; import * as t from 'io-ts'; -import { uniq } from 'lodash'; +import { uniq, mergeWith } from 'lodash'; import { UnknownMLCapabilitiesError, InsufficientMLCapabilities, @@ -40,6 +40,8 @@ import { probabilityRt, } from '../default_api_types'; import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; +import { getServiceOverviewContainerMetadata } from './get_service_overview_container_metadata'; +import { getServiceInstanceContainerMetadata } from './get_service_instance_container_metadata'; import { getServicesDetailedStatistics } from './get_services_detailed_statistics'; import { getServiceDependenciesBreakdown } from './get_service_dependencies_breakdown'; import { getAnomalyTimeseries } from '../../lib/anomaly_detection/get_anomaly_timeseries'; @@ -51,7 +53,7 @@ import { ServiceHealthStatus } from '../../../common/service_health_status'; import { getServiceGroup } from '../service_groups/get_service_group'; import { offsetRt } from '../../../common/comparison_rt'; import { getRandomSampler } from '../../lib/helpers/get_random_sampler'; - +import { getInfraMetricIndices } from '../../lib/helpers/get_infra_metric_indices'; const servicesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/services', params: t.type({ @@ -249,7 +251,7 @@ const serviceMetadataDetailsRoute = createApmServerRoute({ import('./get_service_metadata_details').ServiceMetadataDetails > => { const setup = await setupRequest(resources); - const { params } = resources; + const { params, context, plugins } = resources; const { serviceName } = params.path; const { start, end } = params.query; @@ -261,13 +263,37 @@ const serviceMetadataDetailsRoute = createApmServerRoute({ kuery: '', }); - return getServiceMetadataDetails({ + const serviceMetadataDetails = await getServiceMetadataDetails({ serviceName, setup, searchAggregatedTransactions, start, end, }); + + if (serviceMetadataDetails?.container?.ids) { + const { + savedObjects: { client: savedObjectsClient }, + elasticsearch: { client: esClient }, + } = await context.core; + + const indexName = await getInfraMetricIndices({ + infraPlugin: plugins.infra, + savedObjectsClient, + }); + + const containerMetadata = await getServiceOverviewContainerMetadata({ + esClient: esClient.asCurrentUser, + indexName, + containerIds: serviceMetadataDetails.container.ids, + start, + end, + }); + + return mergeWith(serviceMetadataDetails, containerMetadata); + } + + return serviceMetadataDetails; }, }); @@ -862,16 +888,42 @@ export const serviceInstancesMetadataDetails = createApmServerRoute({ | undefined; }> => { const setup = await setupRequest(resources); - const { serviceName, serviceNodeName } = resources.params.path; - const { start, end } = resources.params.query; + const { params, context, plugins } = resources; + const { serviceName, serviceNodeName } = params.path; + const { start, end } = params.query; - return await getServiceInstanceMetadataDetails({ - setup, - serviceName, - serviceNodeName, - start, - end, - }); + const serviceInstanceMetadataDetails = + await getServiceInstanceMetadataDetails({ + setup, + serviceName, + serviceNodeName, + start, + end, + }); + + if (serviceInstanceMetadataDetails?.container?.id) { + const { + savedObjects: { client: savedObjectsClient }, + elasticsearch: { client: esClient }, + } = await context.core; + + const indexName = await getInfraMetricIndices({ + infraPlugin: plugins.infra, + savedObjectsClient, + }); + + const containerMetadata = await getServiceInstanceContainerMetadata({ + esClient: esClient.asCurrentUser, + indexName, + containerId: serviceInstanceMetadataDetails.container.id, + start, + end, + }); + + return mergeWith(serviceInstanceMetadataDetails, containerMetadata); + } + + return serviceInstanceMetadataDetails; }, }); diff --git a/x-pack/plugins/apm/server/routes/settings/labs/route.ts b/x-pack/plugins/apm/server/routes/settings/labs/route.ts new file mode 100644 index 0000000000000..608fe27106cac --- /dev/null +++ b/x-pack/plugins/apm/server/routes/settings/labs/route.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 { uiSettings } from '@kbn/observability-plugin/server'; +import { createApmServerRoute } from '../../apm_routes/create_apm_server_route'; + +const getLabsRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/settings/labs', + options: { tags: ['access:apm'] }, + handler: async (): Promise<{ labsItems: string[] }> => { + const labsItems = Object.entries(uiSettings) + .filter(([key, value]): boolean | undefined => value.showInLabs) + .map(([key]): string => key); + return { labsItems }; + }, +}); + +export const labsRouteRepository = getLabsRoute; diff --git a/x-pack/plugins/apm/server/routes/transactions/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/routes/transactions/__snapshots__/queries.test.ts.snap index d9e84c1557fa0..3d6e263ecbf98 100644 --- a/x-pack/plugins/apm/server/routes/transactions/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/routes/transactions/__snapshots__/queries.test.ts.snap @@ -284,6 +284,11 @@ Object { exports[`transaction queries fetches transaction trace samples 1`] = ` Object { + "_source": Array [ + "transaction.id", + "trace.id", + "@timestamp", + ], "apm": Object { "events": Array [ "transaction", diff --git a/x-pack/plugins/apm/server/routes/transactions/queries.test.ts b/x-pack/plugins/apm/server/routes/transactions/queries.test.ts index c164d93b4fb52..4770dce0a1cb2 100644 --- a/x-pack/plugins/apm/server/routes/transactions/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/transactions/queries.test.ts @@ -11,8 +11,8 @@ import { SearchParamsMock, } from '../../utils/test_helpers'; import { getTransactionBreakdown } from './breakdown'; -import { getTransactionTraceSamples } from './trace_samples'; import { getTransaction } from './get_transaction'; +import { getTraceSamples } from './trace_samples'; describe('transaction queries', () => { let mock: SearchParamsMock; @@ -56,7 +56,7 @@ describe('transaction queries', () => { it('fetches transaction trace samples', async () => { mock = await inspectSearchParams((setup) => - getTransactionTraceSamples({ + getTraceSamples({ serviceName: 'foo', transactionName: 'bar', transactionType: 'baz', diff --git a/x-pack/plugins/apm/server/routes/transactions/route.ts b/x-pack/plugins/apm/server/routes/transactions/route.ts index 96b1a2a85761a..a96e4416e49e8 100644 --- a/x-pack/plugins/apm/server/routes/transactions/route.ts +++ b/x-pack/plugins/apm/server/routes/transactions/route.ts @@ -16,13 +16,13 @@ import { setupRequest } from '../../lib/helpers/setup_request'; import { getServiceTransactionGroups } from '../services/get_service_transaction_groups'; import { getServiceTransactionGroupDetailedStatisticsPeriods } from '../services/get_service_transaction_group_detailed_statistics'; import { getTransactionBreakdown } from './breakdown'; -import { getTransactionTraceSamples } from './trace_samples'; import { getLatencyPeriods } from './get_latency_charts'; import { getFailedTransactionRatePeriods } from './get_failed_transaction_rate_periods'; import { getColdstartRatePeriods } from '../../lib/transaction_groups/get_coldstart_rate'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; import { offsetRt } from '../../../common/comparison_rt'; +import { getTraceSamples } from './trace_samples'; const transactionGroupsMainStatisticsRoute = createApmServerRoute({ endpoint: @@ -314,7 +314,7 @@ const transactionTraceSamplesRoute = createApmServerRoute({ end, } = params.query; - return getTransactionTraceSamples({ + return getTraceSamples({ environment, kuery, serviceName, diff --git a/x-pack/plugins/apm/server/routes/transactions/trace_samples/get_trace_samples/index.ts b/x-pack/plugins/apm/server/routes/transactions/trace_samples/get_trace_samples/index.ts deleted file mode 100644 index 3f66aaebd0555..0000000000000 --- a/x-pack/plugins/apm/server/routes/transactions/trace_samples/get_trace_samples/index.ts +++ /dev/null @@ -1,106 +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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { rangeQuery, kqlQuery } from '@kbn/observability-plugin/server'; -import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { withApmSpan } from '../../../../utils/with_apm_span'; -import { - SERVICE_NAME, - TRACE_ID, - TRANSACTION_ID, - TRANSACTION_NAME, - TRANSACTION_SAMPLED, - TRANSACTION_TYPE, -} from '../../../../../common/elasticsearch_fieldnames'; -import { environmentQuery } from '../../../../../common/utils/environment_query'; -import { Setup } from '../../../../lib/helpers/setup_request'; - -const TRACE_SAMPLES_SIZE = 500; - -export async function getTraceSamples({ - environment, - kuery, - serviceName, - transactionName, - transactionType, - transactionId, - traceId, - sampleRangeFrom, - sampleRangeTo, - setup, - start, - end, -}: { - environment: string; - kuery: string; - serviceName: string; - transactionName: string; - transactionType: string; - transactionId: string; - traceId: string; - sampleRangeFrom?: number; - sampleRangeTo?: number; - setup: Setup; - start: number; - end: number; -}) { - return withApmSpan('get_trace_samples', async () => { - const { apmEventClient } = setup; - - const commonFilters = [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, - { term: { [TRANSACTION_NAME]: transactionName } }, - ...rangeQuery(start, end), - ...environmentQuery(environment), - ...kqlQuery(kuery), - ] as QueryDslQueryContainer[]; - - if (sampleRangeFrom !== undefined && sampleRangeTo !== undefined) { - commonFilters.push({ - range: { - 'transaction.duration.us': { - gte: sampleRangeFrom, - lte: sampleRangeTo, - }, - }, - }); - } - - async function getTraceSamplesHits() { - const response = await apmEventClient.search('get_trace_samples_hits', { - apm: { - events: [ProcessorEvent.transaction], - }, - body: { - query: { - bool: { - filter: [ - ...commonFilters, - { term: { [TRANSACTION_SAMPLED]: true } }, - ], - should: [ - { term: { [TRACE_ID]: traceId } }, - { term: { [TRANSACTION_ID]: transactionId } }, - ] as QueryDslQueryContainer[], - }, - }, - size: TRACE_SAMPLES_SIZE, - }, - }); - - return response.hits.hits.map((hit) => ({ - transactionId: hit._source.transaction.id, - traceId: hit._source.trace.id, - })); - } - - return { - traceSamples: await getTraceSamplesHits(), - }; - }); -} diff --git a/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts b/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts index 89bae10db5e87..4d7b28ccbbb3f 100644 --- a/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts +++ b/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts @@ -4,14 +4,27 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { Setup } from '../../../lib/helpers/setup_request'; -import { getTraceSamples } from './get_trace_samples'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { rangeQuery, kqlQuery } from '@kbn/observability-plugin/server'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { orderBy } from 'lodash'; import { withApmSpan } from '../../../utils/with_apm_span'; +import { + SERVICE_NAME, + TRACE_ID, + TRANSACTION_ID, + TRANSACTION_NAME, + TRANSACTION_SAMPLED, + TRANSACTION_TYPE, +} from '../../../../common/elasticsearch_fieldnames'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { Setup } from '../../../lib/helpers/setup_request'; -export async function getTransactionTraceSamples({ - kuery, +const TRACE_SAMPLES_SIZE = 500; + +export async function getTraceSamples({ environment, + kuery, serviceName, transactionName, transactionType, @@ -36,20 +49,62 @@ export async function getTransactionTraceSamples({ start: number; end: number; }) { - return withApmSpan('get_transaction_trace_samples', async () => { - return await getTraceSamples({ - environment, - kuery, - serviceName, - transactionName, - transactionType, - transactionId, - traceId, - sampleRangeFrom, - sampleRangeTo, - setup, - start, - end, + return withApmSpan('get_trace_samples', async () => { + const { apmEventClient } = setup; + + const commonFilters = [ + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + { term: { [TRANSACTION_NAME]: transactionName } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ] as QueryDslQueryContainer[]; + + if (sampleRangeFrom !== undefined && sampleRangeTo !== undefined) { + commonFilters.push({ + range: { + 'transaction.duration.us': { + gte: sampleRangeFrom, + lte: sampleRangeTo, + }, + }, + }); + } + + const response = await apmEventClient.search('get_trace_samples_hits', { + apm: { + events: [ProcessorEvent.transaction], + }, + _source: [TRANSACTION_ID, TRACE_ID, '@timestamp'], + body: { + query: { + bool: { + filter: [ + ...commonFilters, + { term: { [TRANSACTION_SAMPLED]: true } }, + ], + should: [ + { term: { [TRACE_ID]: traceId } }, + { term: { [TRANSACTION_ID]: transactionId } }, + ] as QueryDslQueryContainer[], + }, + }, + size: TRACE_SAMPLES_SIZE, + }, }); + + const traceSamples = orderBy( + response.hits.hits.map((hit) => ({ + score: hit._score, + timestamp: hit._source['@timestamp'], + transactionId: hit._source.transaction.id, + traceId: hit._source.trace.id, + })), + ['score', 'timestamp'], + ['desc', 'desc'] + ); + + return { traceSamples }; }); } diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/fields/container.ts b/x-pack/plugins/apm/typings/es_schemas/raw/fields/container.ts index e7a52efc71136..c17517b7c5f2d 100644 --- a/x-pack/plugins/apm/typings/es_schemas/raw/fields/container.ts +++ b/x-pack/plugins/apm/typings/es_schemas/raw/fields/container.ts @@ -6,5 +6,6 @@ */ export interface Container { - id: string; + id?: string | null; + image?: string | null; } diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/fields/kubernetes.ts b/x-pack/plugins/apm/typings/es_schemas/raw/fields/kubernetes.ts index bf24c90a30407..5cf0b497dad18 100644 --- a/x-pack/plugins/apm/typings/es_schemas/raw/fields/kubernetes.ts +++ b/x-pack/plugins/apm/typings/es_schemas/raw/fields/kubernetes.ts @@ -6,5 +6,16 @@ */ export interface Kubernetes { - pod?: { uid: string; [key: string]: unknown }; + pod?: { uid?: string | null; [key: string]: unknown }; + namespace?: string; + replicaset?: { + name?: string; + }; + deployment?: { + name?: string; + }; + container?: { + id?: string; + name?: string; + }; } diff --git a/x-pack/plugins/cases/common/constants.ts b/x-pack/plugins/cases/common/constants.ts index a0488094283d0..e7b7f565ad599 100644 --- a/x-pack/plugins/cases/common/constants.ts +++ b/x-pack/plugins/cases/common/constants.ts @@ -166,6 +166,7 @@ export const PUSH_CASES_CAPABILITY = 'push_cases' as const; */ export const DEFAULT_USER_SIZE = 10; +export const MAX_ASSIGNEES_PER_CASE = 10; /** * Delays diff --git a/x-pack/plugins/cases/common/index.ts b/x-pack/plugins/cases/common/index.ts index e246c98f71c75..489ee28a26cd6 100644 --- a/x-pack/plugins/cases/common/index.ts +++ b/x-pack/plugins/cases/common/index.ts @@ -18,6 +18,8 @@ export { CASES_URL, SECURITY_SOLUTION_OWNER, + OBSERVABILITY_OWNER, + GENERAL_CASES_OWNER, CREATE_CASES_CAPABILITY, DELETE_CASES_CAPABILITY, PUSH_CASES_CAPABILITY, @@ -25,7 +27,13 @@ export { UPDATE_CASES_CAPABILITY, } from './constants'; -export { CommentType, CaseStatuses, getCasesFromAlertsUrl, throwErrors } from './api'; +export { + CommentType, + CaseStatuses, + getCasesFromAlertsUrl, + throwErrors, + ExternalReferenceStorageType, +} from './api'; export type { Case, diff --git a/x-pack/plugins/cases/common/utils/validators.test.ts b/x-pack/plugins/cases/common/utils/validators.test.ts index 8d1d5c52b7b97..e05e9223387b6 100644 --- a/x-pack/plugins/cases/common/utils/validators.test.ts +++ b/x-pack/plugins/cases/common/utils/validators.test.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { isInvalidTag } from './validators'; +import { MAX_ASSIGNEES_PER_CASE } from '../constants'; +import { isInvalidTag, areTotalAssigneesInvalid } from './validators'; describe('validators', () => { describe('isInvalidTag', () => { @@ -26,4 +27,27 @@ describe('validators', () => { expect(isInvalidTag('my string ')).toBe(false); }); }); + + describe('areTotalAssigneesInvalid', () => { + const generateAssignees = (num: number) => + Array.from(Array(num).keys()).map((uid) => { + return { uid: `${uid}` }; + }); + + it('validates undefined assignees correctly', () => { + expect(areTotalAssigneesInvalid()).toBe(false); + }); + + it(`returns false if assignees are less than ${MAX_ASSIGNEES_PER_CASE}`, () => { + expect(areTotalAssigneesInvalid(generateAssignees(3))).toBe(false); + }); + + it(`returns false if assignees are equal to ${MAX_ASSIGNEES_PER_CASE}`, () => { + expect(areTotalAssigneesInvalid(generateAssignees(MAX_ASSIGNEES_PER_CASE))).toBe(false); + }); + + it(`returns true if assignees are greater than ${MAX_ASSIGNEES_PER_CASE}`, () => { + expect(areTotalAssigneesInvalid(generateAssignees(MAX_ASSIGNEES_PER_CASE + 1))).toBe(true); + }); + }); }); diff --git a/x-pack/plugins/cases/common/utils/validators.ts b/x-pack/plugins/cases/common/utils/validators.ts index 5c076944b24c5..0003e3a91fd6f 100644 --- a/x-pack/plugins/cases/common/utils/validators.ts +++ b/x-pack/plugins/cases/common/utils/validators.ts @@ -5,4 +5,15 @@ * 2.0. */ +import { CaseAssignees } from '../api'; +import { MAX_ASSIGNEES_PER_CASE } from '../constants'; + export const isInvalidTag = (value: string) => value.trim() === ''; + +export const areTotalAssigneesInvalid = (assignees?: CaseAssignees): boolean => { + if (assignees == null) { + return false; + } + + return assignees.length > MAX_ASSIGNEES_PER_CASE; +}; diff --git a/x-pack/plugins/cases/public/components/create/assignees.tsx b/x-pack/plugins/cases/public/components/create/assignees.tsx index be4efc0edfed9..05485a2b51a42 100644 --- a/x-pack/plugins/cases/public/components/create/assignees.tsx +++ b/x-pack/plugins/cases/public/components/create/assignees.tsx @@ -21,7 +21,14 @@ import { getUserDisplayName, UserProfile, } from '@kbn/user-profile-components'; -import { UseField, FieldConfig, FieldHook } from '../../common/shared_imports'; +import { MAX_ASSIGNEES_PER_CASE } from '../../../common/constants'; +import { CaseAssignees } from '../../../common/api'; +import { + UseField, + FieldConfig, + FieldHook, + getFieldValidityAndErrorMessage, +} from '../../common/shared_imports'; import { useSuggestUserProfiles } from '../../containers/user_profiles/use_suggest_user_profiles'; import { useCasesContext } from '../cases_context/use_cases_context'; import { useGetCurrentUserProfile } from '../../containers/user_profiles/use_get_current_user_profile'; @@ -46,9 +53,18 @@ interface FieldProps { onSearchComboChange: (value: string) => void; } -const getConfig = (): FieldConfig => ({ +const getConfig = (): FieldConfig => ({ label: i18n.ASSIGNEES, defaultValue: [], + validations: [ + { + validator: ({ value }) => { + if (value.length > MAX_ASSIGNEES_PER_CASE) { + return { message: i18n.INVALID_ASSIGNEES }; + } + }, + }, + ], }); const userProfileToComboBoxOption = (userProfile: UserProfileWithAvatar) => ({ @@ -72,6 +88,7 @@ const AssigneesFieldComponent: React.FC = React.memo( onSearchComboChange, }) => { const { setValue } = field; + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); const onComboChange = useCallback( (currentOptions: EuiComboBoxOptionOption[]) => { @@ -137,6 +154,8 @@ const AssigneesFieldComponent: React.FC = React.memo( ) : undefined } + isInvalid={isInvalid} + error={errorMessage} > ; diff --git a/x-pack/plugins/cases/server/client/cases/create.ts b/x-pack/plugins/cases/server/client/cases/create.ts index 917a88905bf69..225b508f5267a 100644 --- a/x-pack/plugins/cases/server/client/cases/create.ts +++ b/x-pack/plugins/cases/server/client/cases/create.ts @@ -22,8 +22,8 @@ import { excess, CaseSeverity, } from '../../../common/api'; -import { MAX_TITLE_LENGTH } from '../../../common/constants'; -import { isInvalidTag } from '../../../common/utils/validators'; +import { MAX_ASSIGNEES_PER_CASE, MAX_TITLE_LENGTH } from '../../../common/constants'; +import { isInvalidTag, areTotalAssigneesInvalid } from '../../../common/utils/validators'; import { Operations } from '../../authorization'; import { createCaseError } from '../../common/error'; @@ -86,6 +86,12 @@ export const create = async ( } } + if (areTotalAssigneesInvalid(query.assignees)) { + throw Boom.badRequest( + `You cannot assign more than ${MAX_ASSIGNEES_PER_CASE} assignees to a case.` + ); + } + const newCase = await caseService.postNewCase({ attributes: transformNewCase({ user, diff --git a/x-pack/plugins/cases/server/client/cases/types.ts b/x-pack/plugins/cases/server/client/cases/types.ts index 297d42b32ece9..c14cc66210614 100644 --- a/x-pack/plugins/cases/server/client/cases/types.ts +++ b/x-pack/plugins/cases/server/client/cases/types.ts @@ -8,16 +8,16 @@ import { PushToServiceApiParams as JiraPushToServiceApiParams, Incident as JiraIncident, -} from '@kbn/actions-plugin/server/builtin_action_types/jira/types'; +} from '@kbn/stack-connectors-plugin/server/connector_types/cases/jira/types'; import { PushToServiceApiParams as ResilientPushToServiceApiParams, Incident as ResilientIncident, -} from '@kbn/actions-plugin/server/builtin_action_types/resilient/types'; +} from '@kbn/stack-connectors-plugin/server/connector_types/cases/resilient/types'; import { PushToServiceApiParamsITSM as ServiceNowITSMPushToServiceApiParams, PushToServiceApiParamsSIR as ServiceNowSIRPushToServiceApiParams, ServiceNowITSMIncident, -} from '@kbn/actions-plugin/server/builtin_action_types/servicenow/types'; +} from '@kbn/stack-connectors-plugin/server/connector_types/cases/servicenow/types'; import { UserProfile } from '@kbn/security-plugin/common'; import { CaseResponse, ConnectorMappingsAttributes } from '../../../common/api'; diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts index b10fc9ddc9761..6fc738f2525e4 100644 --- a/x-pack/plugins/cases/server/client/cases/update.ts +++ b/x-pack/plugins/cases/server/client/cases/update.ts @@ -14,6 +14,7 @@ import { SavedObject, SavedObjectsFindResponse, SavedObjectsFindResult } from '@ import { nodeBuilder } from '@kbn/es-query'; +import { areTotalAssigneesInvalid } from '../../../common/utils/validators'; import { CasePatchRequest, CasesPatchRequest, @@ -31,6 +32,7 @@ import { import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT, + MAX_ASSIGNEES_PER_CASE, MAX_TITLE_LENGTH, } from '../../../common/constants'; @@ -104,6 +106,27 @@ function throwIfUpdateAssigneesWithoutValidLicense( } } +/** + * Throws an error if any of the requests attempt to add more than + * MAX_ASSIGNEES_PER_CASE to a case + */ +function throwIfTotalAssigneesAreInvalid(requests: UpdateRequestWithOriginalCase[]) { + const requestsUpdatingAssignees = requests.filter( + ({ updateReq }) => updateReq.assignees !== undefined + ); + + if ( + requestsUpdatingAssignees.some(({ updateReq }) => areTotalAssigneesInvalid(updateReq.assignees)) + ) { + const ids = requestsUpdatingAssignees.map(({ updateReq }) => updateReq.id); + throw Boom.badRequest( + `You cannot assign more than ${MAX_ASSIGNEES_PER_CASE} assignees to a case, ids: [${ids.join( + ', ' + )}]` + ); + } +} + /** * Get the id from a reference in a comment for a specific type. */ @@ -332,6 +355,7 @@ export const update = async ( throwIfUpdateOwner(updateCases); throwIfTitleIsInvalid(updateCases); throwIfUpdateAssigneesWithoutValidLicense(updateCases, hasPlatinumLicense); + throwIfTotalAssigneesAreInvalid(updateCases); const updatedCases = await patchCases({ caseService, user, casesToUpdate: updateCases }); diff --git a/x-pack/plugins/cases/tsconfig.json b/x-pack/plugins/cases/tsconfig.json index d7c123ce3970b..b893fcfc9b277 100644 --- a/x-pack/plugins/cases/tsconfig.json +++ b/x-pack/plugins/cases/tsconfig.json @@ -24,6 +24,7 @@ { "path": "../actions/tsconfig.json" }, { "path": "../rule_registry/tsconfig.json" }, { "path": "../triggers_actions_ui/tsconfig.json"}, + { "path": "../stack_connectors/tsconfig.json"}, { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" }, { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, diff --git a/x-pack/plugins/cloud_security_posture/common/schemas/csp_finding.ts b/x-pack/plugins/cloud_security_posture/common/schemas/csp_finding.ts new file mode 100644 index 0000000000000..636182e97e46f --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/common/schemas/csp_finding.ts @@ -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. + */ + +// TODO: this needs to be defined in a versioned schema +import type { EcsEvent } from '@kbn/logging'; +import type { CspRuleMetadata } from './csp_rule_metadata'; + +export interface CspFinding { + '@timestamp': string; + cluster_id: string; + cluster?: { + name?: string; + }; + result: CspFindingResult; + resource: CspFindingResource; + rule: CspRuleMetadata; + host: CspFindingHost; + event: EcsEvent; + agent: CspFindingAgent; + ecs: { + version: string; + }; +} + +interface CspFindingResult { + evaluation: 'passed' | 'failed'; + expected?: Record; + evidence: Record; +} + +interface CspFindingResource { + name: string; + sub_type: string; + raw: object; + id: string; + type: string; + [other_keys: string]: unknown; +} + +interface CspFindingHost { + id: string; + containerized: boolean; + ip: string[]; + mac: string[]; + name: string; + hostname: string; + architecture: string; + os: { + kernel: string; + codename: string; + type: string; + platform: string; + version: string; + family: string; + name: string; + }; + [other_keys: string]: unknown; +} + +interface CspFindingAgent { + version: string; + // ephemeral_id: string; + id: string; + name: string; + type: string; +} diff --git a/x-pack/plugins/cloud_security_posture/common/types.ts b/x-pack/plugins/cloud_security_posture/common/types.ts index f4d139d6a58da..21c33c7f2603a 100644 --- a/x-pack/plugins/cloud_security_posture/common/types.ts +++ b/x-pack/plugins/cloud_security_posture/common/types.ts @@ -33,9 +33,10 @@ export interface PostureTrend extends Stats { export interface Cluster { meta: { clusterId: string; + clusterName?: string; benchmarkName: string; benchmarkId: BenchmarkId; - lastUpdate: number; // unix epoch time + lastUpdate: string; }; stats: Stats; groupedFindingsEvaluation: GroupedFindingsEvaluation[]; @@ -101,3 +102,4 @@ export interface Benchmark { } export type BenchmarkId = CspRuleMetadata['benchmark']['id']; +export type BenchmarkName = CspRuleMetadata['benchmark']['name']; diff --git a/x-pack/plugins/cloud_security_posture/common/utils/subscription.test.ts b/x-pack/plugins/cloud_security_posture/common/utils/subscription.test.ts new file mode 100644 index 0000000000000..e47b887ae520f --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/common/utils/subscription.test.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 type { LicenseType } from '@kbn/licensing-plugin/common/types'; +import { isSubscriptionAllowed } from './subscription'; +import { licenseMock } from '@kbn/licensing-plugin/common/licensing.mock'; + +const ON_PREM_ALLOWED_LICENSES: readonly LicenseType[] = ['enterprise', 'trial']; +const ON_PREM_NOT_ALLOWED_LICENSES: readonly LicenseType[] = ['basic', 'gold', 'platinum']; +const ALL_LICENSE_TYPES: readonly LicenseType[] = [ + 'standard', + ...ON_PREM_NOT_ALLOWED_LICENSES, + ...ON_PREM_NOT_ALLOWED_LICENSES, +]; + +describe('isSubscriptionAllowed', () => { + it('should allow any cloud subscription', () => { + const isCloudEnabled = true; + ALL_LICENSE_TYPES.forEach((licenseType) => { + const license = licenseMock.createLicense({ license: { type: licenseType } }); + expect(isSubscriptionAllowed(isCloudEnabled, license)).toBeTruthy(); + }); + }); + + it('should allow enterprise and trial licenses for on-prem', () => { + const isCloudEnabled = false; + ON_PREM_ALLOWED_LICENSES.forEach((licenseType) => { + const license = licenseMock.createLicense({ license: { type: licenseType } }); + expect(isSubscriptionAllowed(isCloudEnabled, license)).toBeTruthy(); + }); + }); + + it('should not allow enterprise and trial licenses for on-prem', () => { + const isCloudEnabled = false; + ON_PREM_NOT_ALLOWED_LICENSES.forEach((licenseType) => { + const license = licenseMock.createLicense({ license: { type: licenseType } }); + expect(isSubscriptionAllowed(isCloudEnabled, license)).toBeFalsy(); + }); + }); +}); diff --git a/x-pack/plugins/cloud_security_posture/common/utils/subscription.ts b/x-pack/plugins/cloud_security_posture/common/utils/subscription.ts new file mode 100644 index 0000000000000..2d9707681e047 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/common/utils/subscription.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 type { ILicense, LicenseType } from '@kbn/licensing-plugin/common/types'; +import { PLUGIN_NAME } from '..'; + +const MINIMUM_NON_CLOUD_LICENSE_TYPE: LicenseType = 'enterprise'; + +export const isSubscriptionAllowed = (isCloudEnabled?: boolean, license?: ILicense): boolean => { + if (isCloudEnabled) { + return true; + } + + if (!license) { + return false; + } + + const licenseCheck = license.check(PLUGIN_NAME, MINIMUM_NON_CLOUD_LICENSE_TYPE); + return licenseCheck.state === 'valid'; +}; diff --git a/x-pack/plugins/cloud_security_posture/kibana.json b/x-pack/plugins/cloud_security_posture/kibana.json index a5fa332e4d5b4..059de58acc21e 100755 --- a/x-pack/plugins/cloud_security_posture/kibana.json +++ b/x-pack/plugins/cloud_security_posture/kibana.json @@ -10,6 +10,17 @@ "description": "The cloud security posture plugin", "server": true, "ui": true, - "requiredPlugins": ["navigation", "data", "fleet", "unifiedSearch", "taskManager", "security", "charts", "discover"], + "requiredPlugins": [ + "navigation", + "data", + "fleet", + "unifiedSearch", + "taskManager", + "security", + "charts", + "discover", + "cloud", + "licensing" + ], "requiredBundles": ["kibanaReact"] } diff --git a/x-pack/plugins/cloud_security_posture/public/application/setup_context.ts b/x-pack/plugins/cloud_security_posture/public/application/setup_context.ts new file mode 100644 index 0000000000000..574404ace38ce --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/application/setup_context.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 { createContext } from 'react'; + +interface SetupContextValue { + isCloudEnabled?: boolean; +} + +/** + * A utility to pass data from the plugin setup lifecycle stage to application components + */ +export const SetupContext = createContext({}); diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_subscription_status.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_subscription_status.ts new file mode 100644 index 0000000000000..f8bda84dbcb65 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_subscription_status.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 { useContext } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { SetupContext } from '../../application/setup_context'; +import { isSubscriptionAllowed } from '../../../common/utils/subscription'; +import { useKibana } from './use_kibana'; + +const SUBSCRIPTION_QUERY_KEY = 'csp_subscription_query_key'; + +export const useSubscriptionStatus = () => { + const { licensing } = useKibana().services; + const { isCloudEnabled } = useContext(SetupContext); + return useQuery([SUBSCRIPTION_QUERY_KEY], async () => { + const license = await licensing.refresh(); + return isSubscriptionAllowed(isCloudEnabled, license); + }); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx b/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx index 5c6e1a8fa0b47..409460be59287 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx @@ -63,8 +63,8 @@ export const ChartPanel: React.FC = ({ {title && ( - -

{title}

+ +

{title}

)}
diff --git a/x-pack/plugins/cloud_security_posture/public/components/cis_benchmark_icon.tsx b/x-pack/plugins/cloud_security_posture/public/components/cis_benchmark_icon.tsx index 95be9d3cf42ff..a5cc0d34466e2 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/cis_benchmark_icon.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/cis_benchmark_icon.tsx @@ -6,13 +6,16 @@ */ import React from 'react'; -import { EuiIcon } from '@elastic/eui'; +import { EuiIcon, EuiToolTip } from '@elastic/eui'; +import { CSSInterpolation } from '@emotion/serialize'; import type { BenchmarkId } from '../../common/types'; import cisK8sVanillaIcon from '../assets/icons/k8s_logo.svg'; import cisEksIcon from '../assets/icons/cis_eks_logo.svg'; interface Props { type: BenchmarkId; + name?: string; + style?: CSSInterpolation; } const getBenchmarkIdIconType = (props: Props): string => { @@ -26,5 +29,7 @@ const getBenchmarkIdIconType = (props: Props): string => { }; export const CISBenchmarkIcon = (props: Props) => ( - + + + ); diff --git a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.test.tsx index c3cec5ca27774..5eba5fabe51df 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.test.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { useSubscriptionStatus } from '../common/hooks/use_subscription_status'; import Chance from 'chance'; import { DEFAULT_NO_DATA_TEST_SUBJECT, @@ -12,6 +13,7 @@ import { isCommonError, LOADING_STATE_TEST_SUBJECT, PACKAGE_NOT_INSTALLED_TEST_SUBJECT, + SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT, } from './cloud_posture_page'; import { createReactQueryResponse } from '../test/fixtures/react_query'; import { TestProvider } from '../test/test_provider'; @@ -27,6 +29,7 @@ import { useCISIntegrationLink } from '../common/navigation/use_navigate_to_cis_ const chance = new Chance(); jest.mock('../common/api/use_setup_status_api'); jest.mock('../common/navigation/use_navigate_to_cis_integration'); +jest.mock('../common/hooks/use_subscription_status'); describe('', () => { beforeEach(() => { @@ -37,6 +40,13 @@ describe('', () => { data: { status: 'indexed' }, }) ); + + (useSubscriptionStatus as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: true, + }) + ); }); const renderCloudPosturePage = ( @@ -69,10 +79,66 @@ describe('', () => { expect(screen.getByText(children)).toBeInTheDocument(); expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); }); + it('renders default loading state when the subscription query is loading', () => { + (useSubscriptionStatus as jest.Mock).mockImplementation( + () => + createReactQueryResponse({ + status: 'loading', + }) as unknown as UseQueryResult + ); + + const children = chance.sentence(); + renderCloudPosturePage({ children }); + + expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders default error state when the subscription query has an error', () => { + (useSubscriptionStatus as jest.Mock).mockImplementation( + () => + createReactQueryResponse({ + status: 'error', + error: new Error('error'), + }) as unknown as UseQueryResult + ); + + const children = chance.sentence(); + renderCloudPosturePage({ children }); + + expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + + it('renders subscription not allowed prompt if subscription is not installed', () => { + (useSubscriptionStatus as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: false, + }) + ); + + const children = chance.sentence(); + renderCloudPosturePage({ children }); + + expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByText(children)).not.toBeInTheDocument(); + expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.getByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + }); + it('renders integrations installation prompt if integration is not installed', () => { (useCspSetupStatusApi as jest.Mock).mockImplementation(() => createReactQueryResponse({ @@ -88,6 +154,7 @@ describe('', () => { expect(screen.getByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).toBeInTheDocument(); expect(screen.queryByText(children)).not.toBeInTheDocument(); expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); }); @@ -105,6 +172,7 @@ describe('', () => { expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument(); expect(screen.queryByText(children)).not.toBeInTheDocument(); expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); }); @@ -122,6 +190,7 @@ describe('', () => { expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).toBeInTheDocument(); expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByText(children)).not.toBeInTheDocument(); expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); }); @@ -135,6 +204,7 @@ describe('', () => { renderCloudPosturePage({ children, query }); expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByText(children)).not.toBeInTheDocument(); expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); @@ -149,6 +219,7 @@ describe('', () => { renderCloudPosturePage({ children, query }); expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByText(children)).not.toBeInTheDocument(); expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); @@ -177,6 +248,7 @@ describe('', () => { expect(screen.getByText(text, { exact: false })).toBeInTheDocument() ); expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByText(children)).not.toBeInTheDocument(); expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); @@ -209,6 +281,7 @@ describe('', () => { [error, statusCode].forEach((text) => expect(screen.queryByText(text)).not.toBeInTheDocument()); expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByText(children)).not.toBeInTheDocument(); expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); }); @@ -230,6 +303,7 @@ describe('', () => { expect(screen.getByText(loading)).toBeInTheDocument(); expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByText(children)).not.toBeInTheDocument(); expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); }); @@ -245,6 +319,7 @@ describe('', () => { expect(screen.getByTestId(DEFAULT_NO_DATA_TEST_SUBJECT)).toBeInTheDocument(); expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByText(children)).not.toBeInTheDocument(); expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); @@ -273,6 +348,7 @@ describe('', () => { expect(screen.getByText(pageTitle)).toBeInTheDocument(); expect(screen.getAllByText(solution, { exact: false })[0]).toBeInTheDocument(); expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); + expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByText(children)).not.toBeInTheDocument(); expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument(); expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument(); diff --git a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.tsx b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.tsx index 4ded9cb9060bb..6e3c2cf3219d9 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.tsx @@ -7,10 +7,12 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import type { UseQueryResult } from '@tanstack/react-query'; -import { EuiEmptyPrompt } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { NoDataPage } from '@kbn/kibana-react-plugin/public'; import { css } from '@emotion/react'; +import { SubscriptionNotAllowed } from './subscription_not_allowed'; +import { useSubscriptionStatus } from '../common/hooks/use_subscription_status'; import { FullSizeCenteredPage } from './full_size_centered_page'; import { useCspSetupStatusApi } from '../common/api/use_setup_status_api'; import { CspLoadingState } from './csp_loading_state'; @@ -20,6 +22,7 @@ export const LOADING_STATE_TEST_SUBJECT = 'cloud_posture_page_loading'; export const ERROR_STATE_TEST_SUBJECT = 'cloud_posture_page_error'; export const PACKAGE_NOT_INSTALLED_TEST_SUBJECT = 'cloud_posture_page_package_not_installed'; export const DEFAULT_NO_DATA_TEST_SUBJECT = 'cloud_posture_page_no_data'; +export const SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT = 'cloud_posture_page_subscription_not_allowed'; interface CommonError { body: { @@ -55,8 +58,7 @@ const packageNotInstalledRenderer = (cisIntegrationLink?: string) => ( solution={i18n.translate('xpack.csp.cloudPosturePage.packageNotInstalled.solutionNameLabel', { defaultMessage: 'Cloud Security Posture', })} - // TODO: Add real docs link once we have it - docsLink={'https://www.elastic.co/guide/index.html'} + docsLink={'https://ela.st/kspm'} logo={'logoSecurity'} actions={{ elasticAgent: { @@ -65,12 +67,21 @@ const packageNotInstalledRenderer = (cisIntegrationLink?: string) => ( title: i18n.translate('xpack.csp.cloudPosturePage.packageNotInstalled.buttonLabel', { defaultMessage: 'Add a CIS integration', }), - description: i18n.translate( - 'xpack.csp.cloudPosturePage.packageNotInstalled.description', - { - defaultMessage: - 'Use our CIS Kubernetes Benchmark integration to measure your Kubernetes cluster setup against the CIS recommendations.', - } + description: ( + + + + ), + }} + /> ), }, }} @@ -120,28 +131,29 @@ const defaultErrorRenderer = (error: unknown) => ( ); -const defaultNoDataRenderer = () => { - return ( - - - - ); -}; +const defaultNoDataRenderer = () => ( + + + +); + +const subscriptionNotAllowedRenderer = () => ( + + + +); interface CloudPosturePageProps { children: React.ReactNode; @@ -158,10 +170,23 @@ export const CloudPosturePage = ({ errorRender = defaultErrorRenderer, noDataRenderer = defaultNoDataRenderer, }: CloudPosturePageProps) => { + const subscriptionStatus = useSubscriptionStatus(); const getSetupStatus = useCspSetupStatusApi(); const cisIntegrationLink = useCISIntegrationLink(); const render = () => { + if (subscriptionStatus.isError) { + return defaultErrorRenderer(subscriptionStatus.error); + } + + if (subscriptionStatus.isLoading) { + return defaultLoadingRenderer(); + } + + if (!subscriptionStatus.data) { + return subscriptionNotAllowedRenderer(); + } + if (getSetupStatus.isError) { return defaultErrorRenderer(getSetupStatus.error); } diff --git a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page_title.tsx b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page_title.tsx index 88d03a800499e..2000ccef578b8 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page_title.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page_title.tsx @@ -6,40 +6,14 @@ */ import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; -import { css } from '@emotion/react'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; -export const CloudPosturePageTitle = ({ title, isBeta }: { title: string; isBeta: boolean }) => ( +export const CloudPosturePageTitle = ({ title }: { title: string }) => (

{title}

- {isBeta && ( - - - - )}
); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/eks_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/eks_form.tsx index 7cf3fb779942c..b160561807d64 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/eks_form.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/eks_form.tsx @@ -5,7 +5,14 @@ * 2.0. */ import React from 'react'; -import { EuiFormRow, EuiFieldText, EuiDescribedFormGroup, EuiText, EuiSpacer } from '@elastic/eui'; +import { + EuiFormRow, + EuiFieldText, + EuiDescribedFormGroup, + EuiText, + EuiSpacer, + EuiLink, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import type { NewPackagePolicyInput } from '@kbn/fleet-plugin/common'; import { i18n } from '@kbn/i18n'; @@ -98,6 +105,21 @@ const EksForm = ({ onChange, inputs }: Props) => { const eksFormDescription = ( <> + + + + ), + }} + /> + { + const { application } = useKibana().services; + return ( + + + + + } + body={ +

+ + + + ), + }} + /> +

+ } + /> +
+ ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx index af10c55f19ea9..f9172a93457dc 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx @@ -15,10 +15,12 @@ import { Benchmarks } from './benchmarks'; import * as TEST_SUBJ from './test_subjects'; import { useCspBenchmarkIntegrations } from './use_csp_benchmark_integrations'; import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api'; +import { useSubscriptionStatus } from '../../common/hooks/use_subscription_status'; import { useCISIntegrationLink } from '../../common/navigation/use_navigate_to_cis_integration'; jest.mock('./use_csp_benchmark_integrations'); jest.mock('../../common/api/use_setup_status_api'); +jest.mock('../../common/hooks/use_subscription_status'); jest.mock('../../common/navigation/use_navigate_to_cis_integration'); const chance = new Chance(); @@ -31,6 +33,14 @@ describe('', () => { data: { status: 'indexed' }, }) ); + + (useSubscriptionStatus as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: true, + }) + ); + (useCISIntegrationLink as jest.Mock).mockImplementation(() => chance.url()); }); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx index d7b02eac365cb..50aaed4b3a97c 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx @@ -143,7 +143,6 @@ export const Benchmarks = () => { data-test-subj={TEST_SUBJ.BENCHMARKS_PAGE_HEADER} pageTitle={ ', () => { data: { status: 'indexed' }, }) ); + + (useSubscriptionStatus as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: true, + }) + ); }); const renderComplianceDashboardPage = () => { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.tsx index de54f55c5e922..1ea2539b11fdc 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.tsx @@ -33,7 +33,6 @@ export const ComplianceDashboard = () => { bottomBorder pageTitle={ - {complianceData.clusters.map((cluster) => { - const shortId = cluster.meta.clusterId.slice(0, 6); - return ( - - - - - - - -

{cluster.meta.benchmarkName}

-
- -

{`Cluster ID ${shortId}`}

-
- - - - {` ${moment(cluster.meta.lastUpdate).fromNow()}`} - -
- - - - - {INTERNAL_FEATURE_FLAGS.showManageRulesMock && ( - {'Manage Rules'} - )} - -
-
- ( + + + + + + + + - - - handleElementClick(cluster.meta.clusterId, elements) - } - /> - - - - - - handleCellClick(cluster.meta.clusterId, resourceTypeName) - } - onViewAllClick={() => handleViewAllClick(cluster.meta.clusterId)} - /> - - - - - - - ); - })} + + handleElementClick(cluster.meta.clusterId, elements) + } + /> + + + + + + handleCellClick(cluster.meta.clusterId, resourceTypeName) + } + onViewAllClick={() => handleViewAllClick(cluster.meta.clusterId)} + /> + + +
+
+ +
+ ))} ); }; - -const getIntegrationBoxStyle = (euiTheme: EuiThemeComputed) => ({ - border: `1px solid ${euiTheme.colors.lightShade}`, - borderRadius: `${euiTheme.border.radius.medium} 0 0 ${euiTheme.border.radius.medium}`, - background: euiTheme.colors.lightestShade, -}); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cluster_details_box.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cluster_details_box.tsx new file mode 100644 index 0000000000000..daa6e565e9a9b --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cluster_details_box.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 { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiSpacer, + EuiText, + EuiToolTip, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import moment from 'moment'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { INTERNAL_FEATURE_FLAGS } from '../../../../common/constants'; +import { Cluster } from '../../../../common/types'; +import { useNavigateFindings } from '../../../common/hooks/use_navigate_findings'; +import { CISBenchmarkIcon } from '../../../components/cis_benchmark_icon'; + +const defaultClusterTitle = i18n.translate( + 'xpack.csp.dashboard.benchmarkSection.defaultClusterTitle', + { defaultMessage: 'Cluster ID' } +); + +export const ClusterDetailsBox = ({ cluster }: { cluster: Cluster }) => { + const navToFindings = useNavigateFindings(); + + const shortId = cluster.meta.clusterId.slice(0, 6); + const title = cluster.meta.clusterName || defaultClusterTitle; + + const handleClusterTitleClick = (clusterId: string) => { + navToFindings({ cluster_id: clusterId }); + }; + + return ( + + + + + + + + + } + > + handleClusterTitleClick(cluster.meta.clusterId)} color="text"> + +

+ +

+
+
+
+ + + + + +
+ + + + {INTERNAL_FEATURE_FLAGS.showManageRulesMock && ( + + + + + + )} +
+ ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.test.tsx index 2ee05a2565adc..8e330abc93539 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.test.tsx @@ -20,6 +20,7 @@ import type { DataView } from '@kbn/data-plugin/common'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks'; import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api'; +import { useSubscriptionStatus } from '../../common/hooks/use_subscription_status'; import { createReactQueryResponse } from '../../test/fixtures/react_query'; import { useCISIntegrationPoliciesLink } from '../../common/navigation/use_navigate_to_cis_integration_policies'; import { useCISIntegrationLink } from '../../common/navigation/use_navigate_to_cis_integration'; @@ -28,9 +29,11 @@ import { render } from '@testing-library/react'; import { useFindingsEsPit } from './es_pit/use_findings_es_pit'; import { expectIdsInDoc } from '../../test/utils'; import { fleetMock } from '@kbn/fleet-plugin/public/mocks'; +import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; jest.mock('../../common/api/use_latest_findings_data_view'); jest.mock('../../common/api/use_setup_status_api'); +jest.mock('../../common/hooks/use_subscription_status'); jest.mock('../../common/navigation/use_navigate_to_cis_integration_policies'); jest.mock('../../common/navigation/use_navigate_to_cis_integration'); jest.mock('./es_pit/use_findings_es_pit'); @@ -46,6 +49,13 @@ beforeEach(() => { setPitId: () => {}, pitIdRef: chance.guid(), })); + + (useSubscriptionStatus as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: true, + }) + ); }); const renderFindingsPage = () => { @@ -57,6 +67,7 @@ const renderFindingsPage = () => { charts: chartPluginMock.createStartContract(), discover: discoverPluginMock.createStartContract(), fleet: fleetMock.createStartMock(), + licensing: licensingMock.createStart(), }} > diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx index b1b35e3333d0f..583421f645c7a 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx @@ -24,7 +24,7 @@ import { import { assertNever } from '@kbn/std'; import { i18n } from '@kbn/i18n'; import cisLogoIcon from '../../../assets/icons/cis_logo.svg'; -import type { CspFinding } from '../types'; +import { CspFinding } from '../../../../common/schemas/csp_finding'; import { CspEvaluationBadge } from '../../../components/csp_evaluation_badge'; import { ResourceTab } from './resource_tab'; import { JsonTab } from './json_tab'; @@ -32,6 +32,7 @@ import { OverviewTab } from './overview_tab'; import { RuleTab } from './rule_tab'; import type { BenchmarkId } from '../../../../common/types'; import { CISBenchmarkIcon } from '../../../components/cis_benchmark_icon'; +import { BenchmarkName } from '../../../../common/types'; const tabs = [ { @@ -75,13 +76,19 @@ export const Markdown: React.FC> = (props) => ); -export const CisKubernetesIcons = ({ benchmarkId }: { benchmarkId: BenchmarkId }) => ( +export const CisKubernetesIcons = ({ + benchmarkId, + benchmarkName, +}: { + benchmarkId: BenchmarkId; + benchmarkName: BenchmarkName; +}) => ( - + ); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/json_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/json_tab.tsx index ed5845abf19bb..18f36810d03f6 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/json_tab.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/json_tab.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { CodeEditor } from '@kbn/kibana-react-plugin/public'; import { XJsonLang } from '@kbn/monaco'; -import { CspFinding } from '../types'; +import { CspFinding } from '../../../../common/schemas/csp_finding'; const offsetHeight = 120; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/overview_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/overview_tab.tsx index 0decebcbcfe8c..5b5969a3424a2 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/overview_tab.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/overview_tab.tsx @@ -22,7 +22,7 @@ import { CSP_MOMENT_FORMAT } from '../../../common/constants'; import { LATEST_FINDINGS_INDEX_DEFAULT_NS } from '../../../../common/constants'; import { useLatestFindingsDataView } from '../../../common/api/use_latest_findings_data_view'; import { useKibana } from '../../../common/hooks/use_kibana'; -import { CspFinding } from '../types'; +import { CspFinding } from '../../../../common/schemas/csp_finding'; import { CisKubernetesIcons, Markdown, CodeBlock } from './findings_flyout'; type Accordion = Pick & @@ -63,7 +63,12 @@ const getDetailsList = (data: CspFinding, discoverIndexLink: string | undefined) title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.frameworkSourcesTitle', { defaultMessage: 'Framework Sources', }), - description: , + description: ( + + ), }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.cisSectionTitle', { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/resource_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/resource_tab.tsx index 508f295fcecfe..771caa5a070d8 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/resource_tab.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/resource_tab.tsx @@ -18,7 +18,7 @@ import { import React, { useMemo } from 'react'; import { getFlattenedObject } from '@kbn/std'; import { i18n } from '@kbn/i18n'; -import { CspFinding } from '../types'; +import { CspFinding } from '../../../../common/schemas/csp_finding'; const getDescriptionDisplay = (value: unknown) => { if (value === undefined) return 'undefined'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/rule_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/rule_tab.tsx index 81fcd0d7a2f4e..74904041888a4 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/rule_tab.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/rule_tab.tsx @@ -8,7 +8,7 @@ import { EuiBadge, EuiDescriptionList } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { CspFinding } from '../types'; +import { CspFinding } from '../../../../common/schemas/csp_finding'; import { CisKubernetesIcons, Markdown } from './findings_flyout'; export const getRuleList = (rule: CspFinding['rule']) => [ @@ -40,7 +40,9 @@ export const getRuleList = (rule: CspFinding['rule']) => [ title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.frameworkSourcesTitle', { defaultMessage: 'Framework Sources', }), - description: , + description: ( + + ), }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.cisSectionTitle', { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.test.tsx index 4090f80435b46..5f5ee2e5a7ecf 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.test.tsx @@ -24,6 +24,7 @@ import { FindingsEsPitContext } from '../es_pit/findings_es_pit_context'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks'; import { fleetMock } from '@kbn/fleet-plugin/public/mocks'; +import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; jest.mock('../../../common/api/use_latest_findings_data_view'); jest.mock('../../../common/api/use_cis_kubernetes_integration'); @@ -70,6 +71,7 @@ describe('', () => { charts: chartPluginMock.createStartContract(), discover: discoverPluginMock.createStartContract(), fleet: fleetMock.createStartMock(), + licensing: licensingMock.createStart(), }} > diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx index aed0a533622d7..47c5128f919dc 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx @@ -170,7 +170,6 @@ const LatestFindingsPageTitle = () => ( ({ id: chance.word(), + cluster_id: chance.guid(), result: { expected: { source: {}, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.tsx index 195186e3aa323..9eda791f1c3f0 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.tsx @@ -15,8 +15,8 @@ import { type EuiTableFieldDataColumnType, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { CspFinding } from '../../../../common/schemas/csp_finding'; import * as TEST_SUBJECTS from '../test_subjects'; -import type { CspFinding } from '../types'; import { FindingsRuleFlyout } from '../findings_flyout/findings_flyout'; import { baseFindingsColumns, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/use_latest_findings.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/use_latest_findings.ts index 720d21cea2012..1f950c2f59fbd 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/use_latest_findings.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/use_latest_findings.ts @@ -13,9 +13,10 @@ import type { CoreStart } from '@kbn/core/public'; import type { Pagination } from '@elastic/eui'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { i18n } from '@kbn/i18n'; +import { CspFinding } from '../../../../common/schemas/csp_finding'; import { FindingsEsPitContext } from '../es_pit/findings_es_pit_context'; import { extractErrorMessage } from '../../../../common/utils/helpers'; -import type { CspFinding, Sort } from '../types'; +import type { Sort } from '../types'; import { useKibana } from '../../../common/hooks/use_kibana'; import type { FindingsBaseEsQuery } from '../types'; import { FINDINGS_REFETCH_INTERVAL_MS } from '../constants'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx index e8423ad49903c..c90b921c26efb 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx @@ -93,7 +93,6 @@ const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => { { ; - evidence: Record; -} - -interface CspFindingResource { - name: string; - sub_type: string; - raw: object; - id: string; - type: string; - [other_keys: string]: unknown; -} - -interface CspFindingHost { - id: string; - containerized: boolean; - ip: string[]; - mac: string[]; - name: string; - hostname: string; - architecture: string; - os: { - kernel: string; - codename: string; - type: string; - platform: string; - version: string; - family: string; - name: string; - }; - [other_keys: string]: unknown; -} - -interface CspFindingAgent { - version: string; - // ephemeral_id: string; - id: string; - name: string; - type: string; -} - export interface CspFindingsQueryData { page: CspFinding[]; total: number; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/index.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/index.tsx index 48558f087690d..57e3da3658a2e 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/index.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/index.tsx @@ -83,7 +83,6 @@ export const Rules = ({ match: { params } }: RouteComponentProps) ({ useCspIntegrationInfo: jest.fn(), })); jest.mock('../../common/api/use_setup_status_api'); +jest.mock('../../common/hooks/use_subscription_status'); jest.mock('../../common/navigation/use_navigate_to_cis_integration'); const chance = new Chance(); @@ -68,6 +70,14 @@ describe('', () => { data: { status: 'indexed' }, }) ); + + (useSubscriptionStatus as jest.Mock).mockImplementation(() => + createReactQueryResponse({ + status: 'success', + data: true, + }) + ); + (useCISIntegrationLink as jest.Mock).mockImplementation(() => chance.url()); }); diff --git a/x-pack/plugins/cloud_security_posture/public/plugin.tsx b/x-pack/plugins/cloud_security_posture/public/plugin.tsx index 70c874627419b..cf281884f9d91 100755 --- a/x-pack/plugins/cloud_security_posture/public/plugin.tsx +++ b/x-pack/plugins/cloud_security_posture/public/plugin.tsx @@ -17,6 +17,7 @@ import type { CspClientPluginStartDeps, } from './types'; 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( @@ -42,10 +43,13 @@ export class CspPlugin CspClientPluginStartDeps > { + private isCloudEnabled?: boolean; + public setup( core: CoreSetup, plugins: CspClientPluginSetupDeps ): CspClientPluginSetup { + this.isCloudEnabled = plugins.cloud.isCloudEnabled; // Return methods that should be available to other plugins return {}; } @@ -74,7 +78,9 @@ export class CspPlugin ( - + + + ), diff --git a/x-pack/plugins/cloud_security_posture/public/test/test_provider.tsx b/x-pack/plugins/cloud_security_posture/public/test/test_provider.tsx index 09c03c29750a7..f6c636b5ce9ef 100755 --- a/x-pack/plugins/cloud_security_posture/public/test/test_provider.tsx +++ b/x-pack/plugins/cloud_security_posture/public/test/test_provider.tsx @@ -17,6 +17,7 @@ import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks'; import { fleetMock } from '@kbn/fleet-plugin/public/mocks'; +import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; import type { CspClientPluginStartDeps } from '../types'; interface CspAppDeps { @@ -33,6 +34,7 @@ export const TestProvider: React.FC> = ({ charts: chartPluginMock.createStartContract(), discover: discoverPluginMock.createStartContract(), fleet: fleetMock.createStartMock(), + licensing: licensingMock.createStart(), }, params = coreMock.createAppMountParameters(), children, diff --git a/x-pack/plugins/cloud_security_posture/public/types.ts b/x-pack/plugins/cloud_security_posture/public/types.ts index 6d312f4f2a23b..d451f230e20f5 100755 --- a/x-pack/plugins/cloud_security_posture/public/types.ts +++ b/x-pack/plugins/cloud_security_posture/public/types.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { CloudSetup } from '@kbn/cloud-plugin/public'; +import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { ComponentType, ReactNode } from 'react'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; @@ -32,6 +34,7 @@ export interface CspClientPluginSetupDeps { // required data: DataPublicPluginSetup; fleet: FleetSetup; + cloud: CloudSetup; // optional } @@ -42,6 +45,7 @@ export interface CspClientPluginStartDeps { charts: ChartsPluginStart; discover: DiscoverStart; fleet: FleetStart; + licensing: LicensingPluginStart; // optional } diff --git a/x-pack/plugins/cloud_security_posture/server/fleet_integration/fleet_integration.ts b/x-pack/plugins/cloud_security_posture/server/fleet_integration/fleet_integration.ts index 39db06473c9a9..fbea07836f5b2 100644 --- a/x-pack/plugins/cloud_security_posture/server/fleet_integration/fleet_integration.ts +++ b/x-pack/plugins/cloud_security_posture/server/fleet_integration/fleet_integration.ts @@ -20,6 +20,7 @@ import { import { DeepReadonly } from 'utility-types'; import { createCspRuleSearchFilterByPackagePolicy } from '../../common/utils/helpers'; import { + CLOUD_SECURITY_POSTURE_PACKAGE_NAME, CLOUDBEAT_VANILLA, CSP_RULE_SAVED_OBJECT_TYPE, CSP_RULE_TEMPLATE_SAVED_OBJECT_TYPE, @@ -127,6 +128,9 @@ export const isCspPackageInstalled = async ( } }; +export const isCspPackage = (packageName?: string) => + packageName === CLOUD_SECURITY_POSTURE_PACKAGE_NAME; + const generateRulesFromTemplates = ( packagePolicyId: string, policyId: string, 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 c8570cdfc75e3..756eaf261b727 100644 --- a/x-pack/plugins/cloud_security_posture/server/plugin.test.ts +++ b/x-pack/plugins/cloud_security_posture/server/plugin.test.ts @@ -41,6 +41,7 @@ import { SavedObjectsClientContract, } from '@kbn/core/server'; import { securityMock } from '@kbn/security-plugin/server/mocks'; +import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; const chance = new Chance(); @@ -76,6 +77,7 @@ describe('Cloud Security Posture Plugin', () => { data: dataPluginMock.createStartContract(), taskManager: taskManagerMock.createStart(), security: securityMock.createStart(), + licensing: licensingMock.createStart(), }; const contextMock = coreMock.createCustomRequestHandlerContext(mockRouteContext); diff --git a/x-pack/plugins/cloud_security_posture/server/plugin.ts b/x-pack/plugins/cloud_security_posture/server/plugin.ts index 3ff5451e108b8..4603f42a0e09e 100755 --- a/x-pack/plugins/cloud_security_posture/server/plugin.ts +++ b/x-pack/plugins/cloud_security_posture/server/plugin.ts @@ -14,12 +14,17 @@ import type { Plugin, Logger, } from '@kbn/core/server'; -import { DeepReadonly } from 'utility-types'; -import { DeletePackagePoliciesResponse, PackagePolicy } from '@kbn/fleet-plugin/common'; -import { +import type { DeepReadonly } from 'utility-types'; +import type { + DeletePackagePoliciesResponse, + PackagePolicy, + NewPackagePolicy, +} from '@kbn/fleet-plugin/common'; +import type { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; +import { isSubscriptionAllowed } from '../common/utils/subscription'; import type { CspServerPluginSetup, CspServerPluginStart, @@ -32,6 +37,7 @@ import { setupSavedObjects } from './saved_objects'; import { initializeCspIndices } from './create_indices/create_indices'; import { initializeCspTransforms } from './create_transforms/create_transforms'; import { + isCspPackage, isCspPackageInstalled, onPackagePolicyPostCreateCallback, removeCspRulesInstancesCallback, @@ -41,7 +47,6 @@ import { updatePackagePolicyRuntimeCfgVar, getCspRulesSO, } from './routes/configuration/update_rules_configuration'; - import { removeFindingsStatsTask, scheduleFindingsStatsTask, @@ -58,6 +63,7 @@ export class CspPlugin > { private readonly logger: Logger; + private isCloudEnabled?: boolean; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); @@ -77,6 +83,8 @@ export class CspPlugin const coreStartServices = core.getStartServices(); this.setupCspTasks(plugins.taskManager, coreStartServices, this.logger); + this.isCloudEnabled = plugins.cloud.isCloudEnabled; + return {}; } @@ -92,6 +100,26 @@ export class CspPlugin this.initialize(core, plugins.taskManager); } + plugins.fleet.registerExternalCallback( + 'packagePolicyCreate', + async ( + packagePolicy: NewPackagePolicy, + _context: RequestHandlerContext, + _request: KibanaRequest + ): Promise => { + const license = await plugins.licensing.refresh(); + if (isCspPackage(packagePolicy.package?.name)) { + if (!isSubscriptionAllowed(this.isCloudEnabled, license)) { + throw new Error( + 'To use this feature you must upgrade your subscription or start a trial' + ); + } + } + + return packagePolicy; + } + ); + plugins.fleet.registerExternalCallback( 'packagePolicyPostCreate', async ( @@ -99,7 +127,7 @@ export class CspPlugin context: RequestHandlerContext, request: KibanaRequest ): Promise => { - if (packagePolicy.package?.name === CLOUD_SECURITY_POSTURE_PACKAGE_NAME) { + if (isCspPackage(packagePolicy.package?.name)) { await this.initialize(core, plugins.taskManager); const soClient = (await context.core).savedObjects.client; @@ -128,7 +156,7 @@ export class CspPlugin 'postPackagePolicyDelete', async (deletedPackagePolicies: DeepReadonly) => { for (const deletedPackagePolicy of deletedPackagePolicies) { - if (deletedPackagePolicy.package?.name === CLOUD_SECURITY_POSTURE_PACKAGE_NAME) { + if (isCspPackage(deletedPackagePolicy.package?.name)) { const soClient = core.savedObjects.createInternalRepository(); await removeCspRulesInstancesCallback(deletedPackagePolicy, soClient, this.logger); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.test.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.test.ts index 2b3ec5fc00182..03767f366246c 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.test.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.test.ts @@ -11,14 +11,20 @@ const mockClusterBuckets: ClusterBucket[] = [ { key: 'cluster_id', doc_count: 10, - benchmarkName: { - buckets: [{ key: 'CIS Kubernetes', doc_count: 10 }], - }, - benchmarkId: { - buckets: [{ key: 'cis_k8s', doc_count: 10 }], - }, - timestamps: { - buckets: [{ key: 123, doc_count: 1 }], + latestFindingTopHit: { + hits: { + hits: [ + { + _id: '123', + _index: '123', + _source: { + cluster: { name: 'cluster_name' }, + rule: { benchmark: { name: 'CIS Kubernetes', id: 'cis_k8s' } }, + '@timestamp': '123', + }, + }, + ], + }, }, failed_findings: { doc_count: 6, @@ -59,7 +65,8 @@ describe('getClustersFromAggs', () => { expect(clusters).toEqual([ { meta: { - lastUpdate: 123, + lastUpdate: '123', + clusterName: 'cluster_name', clusterId: 'cluster_id', benchmarkName: 'CIS Kubernetes', benchmarkId: 'cis_k8s', diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts index c930dba9fb641..d768d65ac32c7 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts @@ -10,8 +10,11 @@ import type { AggregationsMultiBucketAggregateBase as Aggregation, QueryDslQueryContainer, SearchRequest, + AggregationsTopHitsAggregate, + SearchHit, } from '@elastic/elasticsearch/lib/api/types'; -import type { BenchmarkId, Cluster } from '../../../common/types'; +import { CspFinding } from '../../../common/schemas/csp_finding'; +import type { Cluster } from '../../../common/types'; import { getFailedFindingsFromAggs, failedFindingsAggQuery, @@ -20,8 +23,6 @@ import type { FailedFindingsQueryResult } from './get_grouped_findings_evaluatio import { findingsEvaluationAggsQuery, getStatsFromFindingsEvaluationsAggs } from './get_stats'; import { KeyDocCount } from './compliance_dashboard'; -type UnixEpochTime = number; - export interface ClusterBucket extends FailedFindingsQueryResult, KeyDocCount { failed_findings: { doc_count: number; @@ -29,9 +30,7 @@ export interface ClusterBucket extends FailedFindingsQueryResult, KeyDocCount { passed_findings: { doc_count: number; }; - benchmarkName: Aggregation; - benchmarkId: Aggregation>; - timestamps: Aggregation>; + latestFindingTopHit: AggregationsTopHitsAggregate; } interface ClustersQueryResult { @@ -49,23 +48,10 @@ export const getClustersQuery = (query: QueryDslQueryContainer, pitId: string): field: 'cluster_id', }, aggs: { - benchmarkName: { - terms: { - field: 'rule.benchmark.name', - }, - }, - benchmarkId: { - terms: { - field: 'rule.benchmark.id', - }, - }, - timestamps: { - terms: { - field: '@timestamp', + latestFindingTopHit: { + top_hits: { size: 1, - order: { - _key: 'desc', - }, + sort: [{ '@timestamp': { order: 'desc' } }], }, }, ...failedFindingsAggQuery, @@ -80,23 +66,15 @@ export const getClustersQuery = (query: QueryDslQueryContainer, pitId: string): export const getClustersFromAggs = (clusters: ClusterBucket[]): ClusterWithoutTrend[] => clusters.map((cluster) => { - // get cluster's meta data - const benchmarkNames = cluster.benchmarkName.buckets; - const benchmarkIds = cluster.benchmarkId.buckets; - - if (!Array.isArray(benchmarkIds) || benchmarkIds.length === 0) - throw new Error('missing aggs by benchmarkIds per cluster'); - - if (!Array.isArray(benchmarkNames)) throw new Error('missing aggs by benchmarks per cluster'); - - const timestamps = cluster.timestamps.buckets; - if (!Array.isArray(timestamps)) throw new Error('missing aggs by timestamps per cluster'); + const latestFindingHit: SearchHit = cluster.latestFindingTopHit.hits.hits[0]; + if (!latestFindingHit._source) throw new Error('Missing findings top hits'); const meta = { clusterId: cluster.key, - benchmarkName: benchmarkNames[0].key, - benchmarkId: benchmarkIds[0].key, - lastUpdate: timestamps[0].key, + clusterName: latestFindingHit._source.cluster?.name, + benchmarkName: latestFindingHit._source.rule.benchmark.name, + benchmarkId: latestFindingHit._source.rule.benchmark.id, + lastUpdate: latestFindingHit._source['@timestamp'], }; // get cluster's stats diff --git a/x-pack/plugins/cloud_security_posture/server/types.ts b/x-pack/plugins/cloud_security_posture/server/types.ts index 139cffea1267d..115e353b50d51 100644 --- a/x-pack/plugins/cloud_security_posture/server/types.ts +++ b/x-pack/plugins/cloud_security_posture/server/types.ts @@ -4,10 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { PluginSetup as DataPluginSetup, PluginStart as DataPluginStart, } from '@kbn/data-plugin/server'; +import type { LicensingPluginStart } from '@kbn/licensing-plugin/server'; import { TaskManagerSetupContract, TaskManagerStartContract, @@ -42,6 +44,7 @@ export interface CspServerPluginSetupDeps { data: DataPluginSetup; taskManager: TaskManagerSetupContract; security: SecurityPluginSetup; + cloud: CloudSetup; // optional } @@ -51,6 +54,7 @@ export interface CspServerPluginStartDeps { fleet: FleetStartContract; taskManager: TaskManagerStartContract; security: SecurityPluginStart; + licensing: LicensingPluginStart; } export type CspServerPluginStartServices = Promise< diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index 3310ed5d69e4f..3fe7d29d3801a 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -7,6 +7,8 @@ import { i18n } from '@kbn/i18n'; +import { IngestPipelineParams } from './types/connectors'; + export const ENTERPRISE_SEARCH_OVERVIEW_PLUGIN = { ID: 'enterpriseSearch', NAME: i18n.translate('xpack.enterpriseSearch.overview.productName', { @@ -109,6 +111,7 @@ export const ENTERPRISE_SEARCH_KIBANA_COOKIE = '_enterprise_search'; export const ENTERPRISE_SEARCH_RELEVANCE_LOGS_SOURCE_ID = 'ent-search-logs'; export const ENTERPRISE_SEARCH_AUDIT_LOGS_SOURCE_ID = 'ent-search-audit-logs'; +export const ENTERPRISE_SEARCH_ANALYTICS_LOGS_SOURCE_ID = 'ent-search-analytics-logs'; export const APP_SEARCH_URL = '/app/enterprise_search/app_search'; export const ENTERPRISE_SEARCH_ELASTICSEARCH_URL = '/app/enterprise_search/elasticsearch'; @@ -117,3 +120,11 @@ export const WORKPLACE_SEARCH_URL = '/app/enterprise_search/workplace_search'; export const ENTERPRISE_SEARCH_DOCUMENTS_DEFAULT_DOC_COUNT = 25; export const ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE = 'elastic-crawler'; + +export const DEFAULT_PIPELINE_NAME = 'ent-search-generic-ingestion'; +export const DEFAULT_PIPELINE_VALUES: IngestPipelineParams = { + extract_binary_content: true, + name: DEFAULT_PIPELINE_NAME, + reduce_whitespace: true, + run_ml_inference: false, +}; diff --git a/x-pack/plugins/enterprise_search/common/types/error_codes.ts b/x-pack/plugins/enterprise_search/common/types/error_codes.ts index e0e4070e167e1..8ceb77281a1e5 100644 --- a/x-pack/plugins/enterprise_search/common/types/error_codes.ts +++ b/x-pack/plugins/enterprise_search/common/types/error_codes.ts @@ -13,6 +13,7 @@ export enum ErrorCode { CRAWLER_ALREADY_EXISTS = 'crawler_already_exists', INDEX_ALREADY_EXISTS = 'index_already_exists', INDEX_NOT_FOUND = 'index_not_found', + PIPELINE_ALREADY_EXISTS = 'pipeline_already_exists', RESOURCE_NOT_FOUND = 'resource_not_found', UNAUTHORIZED = 'unauthorized', UNCAUGHT_EXCEPTION = 'uncaught_exception', diff --git a/x-pack/plugins/enterprise_search/common/types/indices.ts b/x-pack/plugins/enterprise_search/common/types/indices.ts index d047ec9ba36d7..3fbd9dccb3efd 100644 --- a/x-pack/plugins/enterprise_search/common/types/indices.ts +++ b/x-pack/plugins/enterprise_search/common/types/indices.ts @@ -36,6 +36,7 @@ export interface ElasticsearchIndex { export interface ConnectorIndex extends ElasticsearchIndex { connector: Connector; } + export interface CrawlerIndex extends ElasticsearchIndex { crawler: Crawler; connector?: Connector; 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 1007c3f4421af..dc960fb103ddd 100644 --- a/x-pack/plugins/enterprise_search/common/ui_settings_keys.ts +++ b/x-pack/plugins/enterprise_search/common/ui_settings_keys.ts @@ -7,3 +7,4 @@ export const enterpriseSearchFeatureId = 'enterpriseSearch'; export const enableIndexPipelinesTab = 'enterpriseSearch:enableIndexTransformsTab'; +export const enableBehavioralAnalyticsSection = 'enterpriseSearch:enableBehavioralAnalyticsSection'; 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 8d3ff402c41f8..a6345775b1a9c 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 @@ -6,12 +6,15 @@ */ import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; +import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; +import { Capabilities } from '@kbn/core/public'; import { securityMock } from '@kbn/security-plugin/public/mocks'; import { mockHistory } from '../react_router/state.mock'; export const mockKibanaValues = { + capabilities: {} as Capabilities, config: { host: 'http://localhost:3002' }, charts: chartPluginMock.createStartContract(), cloud: { @@ -25,6 +28,7 @@ export const mockKibanaValues = { hasAppSearchAccess: true, hasWorkplaceSearchAccess: true, }, + uiSettings: uiSettingsServiceMock.createStartContract(), security: securityMock.createStart(), setBreadcrumbs: jest.fn(), setChromeIsVisible: jest.fn(), diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_events.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_events.test.tsx new file mode 100644 index 0000000000000..9a2366384cfc3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_events.test.tsx @@ -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 '../../../__mocks__/shallow_useeffect.mock'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { AnalyticsCollection } from '../../../../../common/types/analytics'; +import { EntSearchLogStream } from '../../../shared/log_stream'; + +import { AnalyticsCollectionEvents } from './analytics_collection_events'; + +describe('AnalyticsCollectionEvents', () => { + const analyticsCollections: AnalyticsCollection = { + event_retention_day_length: 180, + id: '1', + name: 'example', + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders', () => { + const expectedQuery = '_index: logs-elastic_analytics.events-example*'; + + const wrapper = shallow(); + expect(wrapper.find(EntSearchLogStream).prop('query')).toEqual(expectedQuery); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_events.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_events.tsx new file mode 100644 index 0000000000000..25feb0ffcd98e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_events.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 React from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { ENTERPRISE_SEARCH_ANALYTICS_LOGS_SOURCE_ID } from '../../../../../common/constants'; +import { AnalyticsCollection } from '../../../../../common/types/analytics'; + +import { EntSearchLogStream } from '../../../shared/log_stream'; + +interface AnalyticsCollectionEventsProps { + collection: AnalyticsCollection; +} + +export const AnalyticsCollectionEvents: React.FC = ({ + collection, +}) => { + return ( + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.tsx index 4c8e5a36e4e0f..231dd04893bd5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_view.tsx @@ -27,6 +27,7 @@ import { COLLECTION_CREATION_PATH, COLLECTION_VIEW_PATH } from '../../routes'; import { EnterpriseSearchAnalyticsPageTemplate } from '../layout/page_template'; +import { AnalyticsCollectionEvents } from './analytics_collection_events'; import { AnalyticsCollectionIntegrate } from './analytics_collection_integrate'; import { AnalyticsCollectionSettings } from './analytics_collection_settings'; @@ -147,6 +148,7 @@ export const AnalyticsCollectionView: React.FC = () => { {section === 'integrate' && ( )} + {section === 'events' && } ) : ( { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders ', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_feature_disabled_error/analytics_feature_disabled_error.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_feature_disabled_error/analytics_feature_disabled_error.tsx new file mode 100644 index 0000000000000..1cbcf0b208cb7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_feature_disabled_error/analytics_feature_disabled_error.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiEmptyPrompt } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; + +export const AnalyticsFeatureDisabledError: React.FC = () => { + return ( + + + {i18n.translate('xpack.enterpriseSearch.analytics.featureDisabledState.title', { + defaultMessage: 'Behavioural Analytics is disabled', + })} + + } + titleSize="l" + /> + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/index.test.tsx index 60fda2a255cda..4a8fcf7573a36 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/index.test.tsx @@ -10,26 +10,45 @@ import '../__mocks__/kea_logic'; import '../__mocks__/shallow_useeffect.mock'; import '../__mocks__/enterprise_search_url.mock'; +import { mockKibanaValues } from '../__mocks__/kea_logic'; + import React from 'react'; import { shallow } from 'enzyme'; import { VersionMismatchPage } from '../shared/version_mismatch'; +import { AnalyticsFeatureDisabledError } from './components/analytics_feature_disabled_error/analytics_feature_disabled_error'; import { AnalyticsOverview } from './components/analytics_overview/analytics_overview'; import { Analytics } from '.'; describe('EnterpriseSearchAnalytics', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('always renders the overview', () => { + mockKibanaValues.uiSettings.get.mockReturnValue(true); + const wrapper = shallow(); expect(wrapper.find(AnalyticsOverview)).toHaveLength(1); }); it('renders VersionMismatchPage when there are mismatching versions', () => { + mockKibanaValues.uiSettings.get.mockReturnValue(true); + const wrapper = shallow(); expect(wrapper.find(VersionMismatchPage)).toHaveLength(1); }); + + it('renders behavioural analytics is disabled message', () => { + mockKibanaValues.uiSettings.get.mockReturnValue(false); + + const wrapper = shallow(); + + expect(wrapper.find(AnalyticsFeatureDisabledError)).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/index.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/index.tsx index b2f7d20bf261c..eaed153a01b7f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/index.tsx @@ -8,13 +8,18 @@ import React from 'react'; import { Route, Switch } from 'react-router-dom'; +import { useValues } from 'kea'; + import { isVersionMismatch } from '../../../common/is_version_mismatch'; import { InitialAppData } from '../../../common/types'; +import { enableBehavioralAnalyticsSection } from '../../../common/ui_settings_keys'; +import { KibanaLogic } from '../shared/kibana'; import { VersionMismatchPage } from '../shared/version_mismatch'; import { AddAnalyticsCollection } from './components/add_analytics_collections/add_analytics_collection'; import { AnalyticsCollectionView } from './components/analytics_collection_view/analytics_collection_view'; +import { AnalyticsFeatureDisabledError } from './components/analytics_feature_disabled_error/analytics_feature_disabled_error'; import { AnalyticsOverview } from './components/analytics_overview/analytics_overview'; import { ROOT_PATH, COLLECTION_CREATION_PATH, COLLECTION_VIEW_PATH } from './routes'; @@ -22,6 +27,14 @@ import { ROOT_PATH, COLLECTION_CREATION_PATH, COLLECTION_VIEW_PATH } from './rou export const Analytics: React.FC = (props) => { const { enterpriseSearchVersion, kibanaVersion } = props; const incompatibleVersions = isVersionMismatch(enterpriseSearchVersion, kibanaVersion); + const { uiSettings } = useValues(KibanaLogic); + + const analyticsSectionEnabled = + uiSettings?.get(enableBehavioralAnalyticsSection) ?? false; + + if (!analyticsSectionEnabled) { + return ; + } return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/get_default_pipeline_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/get_default_pipeline_api_logic.test.ts new file mode 100644 index 0000000000000..6246fb20df3b3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/get_default_pipeline_api_logic.test.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 { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test-jest-helpers'; + +import { getDefaultPipeline } from './get_default_pipeline_api_logic'; + +describe('getDefaultPipelineApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('updatePipeline', () => { + it('calls correct api', async () => { + const promise = Promise.resolve('result'); + http.get.mockReturnValue(promise); + const result = getDefaultPipeline(); + await nextTick(); + expect(http.get).toHaveBeenCalledWith( + '/internal/enterprise_search/connectors/default_pipeline' + ); + await expect(result).resolves.toEqual('result'); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/get_default_pipeline_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/get_default_pipeline_api_logic.ts new file mode 100644 index 0000000000000..eddc2f2ced2d5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/get_default_pipeline_api_logic.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 { IngestPipelineParams } from '../../../../../common/types/connectors'; + +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export type FetchDefaultPipelineResponse = IngestPipelineParams; + +export const getDefaultPipeline = async (): Promise => { + const route = '/internal/enterprise_search/connectors/default_pipeline'; + + return await HttpLogic.values.http.get(route); +}; + +export const FetchDefaultPipelineApiLogic = createApiLogic( + ['content', 'get_default_pipeline_api_logic'], + getDefaultPipeline +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/set_native_connector_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/set_native_connector_api_logic.ts new file mode 100644 index 0000000000000..5b23ef9a5c78a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/set_native_connector_api_logic.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. + */ + +import { ConnectorStatus } from '../../../../../common/types/connectors'; +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; +import { NativeConnector } from '../../components/search_index/connector/types'; + +export interface SetNativeConnectorArgs { + connectorId: string; + nativeConnector: NativeConnector; +} + +export interface SetNativeConnectorResponse { + connectorId: string; + nativeConnector: NativeConnector; +} + +export const setNativeConnector = async ({ + connectorId, + nativeConnector, +}: SetNativeConnectorArgs) => { + const { configuration, serviceType } = nativeConnector; + + await HttpLogic.values.http.put( + `/internal/enterprise_search/connectors/${connectorId}/service_type`, + { + body: JSON.stringify({ serviceType }), + } + ); + + await HttpLogic.values.http.post( + `/internal/enterprise_search/connectors/${connectorId}/configuration`, + { + body: JSON.stringify(configuration), + } + ); + + await HttpLogic.values.http.put(`/internal/enterprise_search/connectors/${connectorId}/status`, { + body: JSON.stringify({ status: ConnectorStatus.NEEDS_CONFIGURATION }), + }); + + return { connectorId }; +}; + +export const SetNativeConnectorLogic = createApiLogic( + ['content', 'service_type_connector_api_logic'], + setNativeConnector +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/start_sync_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/start_sync_api_logic.test.ts index 7496557996868..eeb7ce06a1e16 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/start_sync_api_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/start_sync_api_logic.test.ts @@ -23,7 +23,22 @@ describe('startSync', () => { const result = startSync({ connectorId: 'connectorId' }); await nextTick(); expect(http.post).toHaveBeenCalledWith( - '/internal/enterprise_search/connectors/connectorId/start_sync' + '/internal/enterprise_search/connectors/connectorId/start_sync', + { body: '{}' } + ); + await expect(result).resolves.toEqual('result'); + }); + + it('calls correct api with nextSyncConfig', async () => { + const promise = Promise.resolve('result'); + http.post.mockReturnValue(promise); + const nextSyncConfig = { max_crawl_depth: 3 }; + const result = startSync({ connectorId: 'connectorId', nextSyncConfig }); + const body = JSON.stringify({ nextSyncConfig: JSON.stringify(nextSyncConfig) }); + await nextTick(); + expect(http.post).toHaveBeenCalledWith( + '/internal/enterprise_search/connectors/connectorId/start_sync', + { body } ); await expect(result).resolves.toEqual('result'); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/start_sync_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/start_sync_api_logic.ts index b2f9a2638e1c0..4a97edfa528a8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/start_sync_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/start_sync_api_logic.ts @@ -7,14 +7,20 @@ import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; import { HttpLogic } from '../../../shared/http'; +import { CrawlRequestOverrides } from '../../components/search_index/crawler/crawler_logic'; export interface StartSyncArgs { connectorId: string; + nextSyncConfig?: CrawlRequestOverrides; } -export const startSync = async ({ connectorId }: StartSyncArgs) => { +export const startSync = async ({ connectorId, nextSyncConfig }: StartSyncArgs) => { const route = `/internal/enterprise_search/connectors/${connectorId}/start_sync`; - return await HttpLogic.values.http.post(route); + return await HttpLogic.values.http.post(route, { + // ConnectorConfiguration type is a record of key-value pair where value is a string + // To store nextSyncConfig into ConnectorConfiguration the object should be casted to string + body: JSON.stringify({ nextSyncConfig: JSON.stringify(nextSyncConfig) }), + }); }; export const StartSyncApiLogic = createApiLogic(['start_sync_api_logic'], startSync); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_default_pipeline_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_default_pipeline_api_logic.test.ts new file mode 100644 index 0000000000000..b7c28f8f54fb9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_default_pipeline_api_logic.test.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 { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test-jest-helpers'; + +import { updateDefaultPipeline } from './update_default_pipeline_api_logic'; + +describe('updateDefaultPipelineApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('updateDefaultPipeline', () => { + it('calls correct api', async () => { + const promise = Promise.resolve('result'); + http.post.mockReturnValue(promise); + const pipeline = { + extract_binary_content: true, + name: 'pipelineName', + reduce_whitespace: false, + run_ml_inference: true, + }; + const result = updateDefaultPipeline(pipeline); + await nextTick(); + expect(http.put).toHaveBeenCalledWith( + '/internal/enterprise_search/connectors/default_pipeline', + { + body: JSON.stringify(pipeline), + } + ); + await expect(result).resolves.toEqual(pipeline); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_default_pipeline_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_default_pipeline_api_logic.ts new file mode 100644 index 0000000000000..612403a1f0472 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_default_pipeline_api_logic.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { IngestPipelineParams } from '../../../../../common/types/connectors'; + +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export type PostDefaultPipelineResponse = IngestPipelineParams; +export type PostDefaultPipelineArgs = IngestPipelineParams; + +export const updateDefaultPipeline = async ( + pipeline: IngestPipelineParams +): Promise => { + const route = '/internal/enterprise_search/connectors/default_pipeline'; + + await HttpLogic.values.http.put(route, { body: JSON.stringify(pipeline) }); + + return pipeline; +}; + +export const UpdateDefaultPipelineApiLogic = createApiLogic( + ['content', 'update_default_pipeline_api_logic'], + updateDefaultPipeline +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_pipeline_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_pipeline_api_logic.test.ts new file mode 100644 index 0000000000000..e50400a5087a8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_pipeline_api_logic.test.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 { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test-jest-helpers'; + +import { updatePipeline } from './update_pipeline_api_logic'; + +describe('updatePipelineApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('updatePipeline', () => { + it('calls correct api', async () => { + const promise = Promise.resolve('result'); + http.post.mockReturnValue(promise); + const pipeline = { + extract_binary_content: true, + name: 'pipelineName', + reduce_whitespace: false, + run_ml_inference: true, + }; + const result = updatePipeline({ connectorId: 'connector_id', pipeline }); + await nextTick(); + expect(http.put).toHaveBeenCalledWith( + '/internal/enterprise_search/connectors/connector_id/pipeline', + { + body: JSON.stringify(pipeline), + } + ); + await expect(result).resolves.toEqual({ connectorId: 'connector_id', pipeline }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_pipeline_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_pipeline_api_logic.ts new file mode 100644 index 0000000000000..b3f3b2f673622 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_pipeline_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 { IngestPipelineParams } from '../../../../../common/types/connectors'; +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface PostPipelineArgs { + connectorId: string; + pipeline: IngestPipelineParams; +} + +export interface PostPipelineResponse { + connectorId: string; + pipeline: IngestPipelineParams; +} + +export const updatePipeline = async ({ + connectorId, + pipeline, +}: PostPipelineArgs): Promise => { + const route = `/internal/enterprise_search/connectors/${connectorId}/pipeline`; + + await HttpLogic.values.http.put(route, { + body: JSON.stringify(pipeline), + }); + return { connectorId, pipeline }; +}; + +export const UpdatePipelineApiLogic = createApiLogic( + ['content', 'update_pipeline_api_logic'], + updatePipeline +); 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 b6c329b78ac2a..4b11aa699b081 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 @@ -57,6 +57,11 @@ export const CRAWLER_DOMAIN_CONFIG_FROM_SERVER: DomainConfigFromServer = { }; export const CRAWLER_DOMAIN_FROM_SERVER: CrawlerDomainFromServer = { + auth: { + type: 'basic', + username: 'username', + password: 'password', + }, available_deduplication_fields: ['title', 'url'], crawl_rules: [CRAWL_RULE], created_on: '1657234422', @@ -84,6 +89,11 @@ export const CRAWLER_DOMAIN_CONFIG: DomainConfig = { }; export const CRAWLER_DOMAIN: CrawlerDomain = { + auth: { + type: 'basic', + username: 'username', + password: 'password', + }, availableDeduplicationFields: ['title', 'url'], crawlRules: [CRAWL_RULE], createdOn: '1657234422', 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 c29101c205c40..f828d34bdde12 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 @@ -63,9 +63,23 @@ export enum CrawlType { Partial = 'partial', } +export interface BasicCrawlerAuth { + password: string; + type: 'basic'; + username: string; +} + +export interface RawCrawlerAuth { + header: string; + type: 'raw'; +} + +export type CrawlerAuth = BasicCrawlerAuth | RawCrawlerAuth | null; + // Server export interface CrawlerDomainFromServer { + auth: CrawlerAuth; available_deduplication_fields: string[]; crawl_rules: CrawlRule[]; created_on: string; @@ -150,6 +164,7 @@ export interface DomainConfigFromServer { // Client export interface CrawlerDomain { + auth: CrawlerAuth; availableDeduplicationFields: string[]; crawlRules: CrawlRule[]; createdOn: string; 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 b4853875dc9f7..ab47d8a575c5b 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 @@ -26,25 +26,30 @@ import { DomainConfigFromServer, CrawlerDomainsWithMetaFromServer, CrawlerDomainsWithMeta, + BasicCrawlerAuth, + CrawlerAuth, + RawCrawlerAuth, } from './types'; export function crawlerDomainServerToClient(payload: CrawlerDomainFromServer): CrawlerDomain { const { - id, - name, - sitemaps, - created_on: createdOn, - last_visited_at: lastCrawl, - document_count: documentCount, + auth, + available_deduplication_fields: availableDeduplicationFields, crawl_rules: crawlRules, - default_crawl_rule: defaultCrawlRule, - entry_points: entryPoints, + created_on: createdOn, deduplication_enabled: deduplicationEnabled, deduplication_fields: deduplicationFields, - available_deduplication_fields: availableDeduplicationFields, + default_crawl_rule: defaultCrawlRule, + document_count: documentCount, + entry_points: entryPoints, + id, + last_visited_at: lastCrawl, + name, + sitemaps, } = payload; const clientPayload: CrawlerDomain = { + auth, availableDeduplicationFields, crawlRules, createdOn, @@ -235,3 +240,11 @@ export const crawlerDomainsWithMetaServerToClient = ({ domains: results.map(crawlerDomainServerToClient), meta, }); + +export function isBasicCrawlerAuth(auth: CrawlerAuth): auth is BasicCrawlerAuth { + return auth !== null && (auth as BasicCrawlerAuth).type === 'basic'; +} + +export function isRawCrawlerAuth(auth: CrawlerAuth): auth is RawCrawlerAuth { + return auth !== null && (auth as RawCrawlerAuth).type === 'raw'; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/create_custom_pipeline_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/create_custom_pipeline_api_logic.ts new file mode 100644 index 0000000000000..9b3f8e9bd5c03 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/create_custom_pipeline_api_logic.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface CreateCustomPipelineApiLogicArgs { + indexName: string; +} + +export interface CreateCustomPipelineApiLogicResponse { + created: string[]; +} + +export const createCustomPipeline = async ({ + indexName, +}: CreateCustomPipelineApiLogicArgs): Promise => { + const route = `/internal/enterprise_search/indices/${indexName}/pipelines`; + const result = await HttpLogic.values.http.post(route); + return result; +}; + +export const CreateCustomPipelineApiLogic = createApiLogic( + ['content', 'create_custom_pipeline_api_logic'], + createCustomPipeline +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/fetch_custom_pipeline_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/fetch_custom_pipeline_api_logic.test.ts new file mode 100644 index 0000000000000..db82d76bcdce8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/fetch_custom_pipeline_api_logic.test.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 { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test-jest-helpers'; + +import { fetchCustomPipeline } from './fetch_custom_pipeline_api_logic'; + +describe('updatePipelineApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('updatePipeline', () => { + it('calls correct api', async () => { + const promise = Promise.resolve('result'); + http.get.mockReturnValue(promise); + const result = fetchCustomPipeline({ indexName: 'index_20' }); + await nextTick(); + expect(http.get).toHaveBeenCalledWith( + '/internal/enterprise_search/indices/index_20/pipelines' + ); + await expect(result).resolves.toEqual('result'); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/fetch_custom_pipeline_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/fetch_custom_pipeline_api_logic.ts new file mode 100644 index 0000000000000..bc605e6343287 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/index/fetch_custom_pipeline_api_logic.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; + +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface FetchCustomPipelineApiLogicArgs { + indexName: string; +} + +export type FetchCustomPipelineApiLogicResponse = Record; + +export const fetchCustomPipeline = async ({ + indexName, +}: FetchCustomPipelineApiLogicArgs): Promise => { + const route = `/internal/enterprise_search/indices/${indexName}/pipelines`; + const result = await HttpLogic.values.http.get(route); + return result; +}; + +export const FetchCustomPipelineApiLogic = createApiLogic( + ['content', 'fetch_custom_pipeline_api_logic'], + fetchCustomPipeline +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/create_ml_inference_pipeline.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/create_ml_inference_pipeline.test.ts new file mode 100644 index 0000000000000..9750be9166647 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/create_ml_inference_pipeline.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { + createMlInferencePipeline, + CreateMlInferencePipelineApiLogicArgs, + CreateMlInferencePipelineResponse, +} from './create_ml_inference_pipeline'; + +describe('CreateMlInferencePipelineApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('createMlInferencePipeline', () => { + it('calls the api', async () => { + const response: Promise = Promise.resolve({ + created: 'ml-inference-unit-test', + }); + http.post.mockReturnValue(response); + + const args: CreateMlInferencePipelineApiLogicArgs = { + indexName: 'unit-test-index', + modelId: 'test-model', + pipelineName: 'unit-test', + sourceField: 'body', + }; + const result = await createMlInferencePipeline(args); + expect(http.post).toHaveBeenCalledWith( + '/internal/enterprise_search/indices/unit-test-index/ml_inference/pipeline_processors', + { + body: '{"pipeline_name":"unit-test","model_id":"test-model","source_field":"body"}', + } + ); + expect(result).toEqual({ + created: 'ml-inference-unit-test', + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/create_ml_inference_pipeline.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/create_ml_inference_pipeline.ts new file mode 100644 index 0000000000000..3935cfa30e9f8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/create_ml_inference_pipeline.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 { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface CreateMlInferencePipelineApiLogicArgs { + indexName: string; + pipelineName: string; + modelId: string; + sourceField: string; + destinationField?: string; +} + +export interface CreateMlInferencePipelineResponse { + created: string; +} + +export const createMlInferencePipeline = async ( + args: CreateMlInferencePipelineApiLogicArgs +): Promise => { + const route = `/internal/enterprise_search/indices/${args.indexName}/ml_inference/pipeline_processors`; + const params = { + pipeline_name: args.pipelineName, + model_id: args.modelId, + source_field: args.sourceField, + destination_field: args.destinationField, + }; + return await HttpLogic.values.http.post(route, { + body: JSON.stringify(params), + }); +}; + +export const CreateMlInferencePipelineApiLogic = createApiLogic( + ['create_ml_inference_pipeline_api_logic'], + createMlInferencePipeline +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_models_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_models_logic.test.ts new file mode 100644 index 0000000000000..5ba9ef36197a9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_models_logic.test.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; + +import { getMLModels } from './ml_models_logic'; + +describe('MLModelsApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('getMLModels', () => { + it('calls the ml api', async () => { + const response: Promise = Promise.resolve([ + { + inference_config: {}, + input: { + field_names: [], + }, + model_id: 'a-model-001', + model_type: 'pytorch', + tags: ['pytorch', 'ner'], + version: '1', + }, + { + inference_config: {}, + input: { + field_names: [], + }, + model_id: 'a-model-002', + model_type: 'lang_ident', + tags: [], + version: '2', + }, + ]); + http.get.mockReturnValue(response); + const result = await getMLModels(); + expect(http.get).toHaveBeenCalledWith('/api/ml/trained_models', { + query: { size: 1000, with_pipelines: true }, + }); + expect(result).toEqual([ + { + inference_config: {}, + input: { + field_names: [], + }, + model_id: 'a-model-001', + model_type: 'pytorch', + tags: ['pytorch', 'ner'], + version: '1', + }, + { + inference_config: {}, + input: { + field_names: [], + }, + model_id: 'a-model-002', + model_type: 'lang_ident', + tags: [], + version: '2', + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_models_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_models_logic.ts new file mode 100644 index 0000000000000..0f0ead7bb0642 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_models_logic.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 { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; + +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export const getMLModels = async (size: number = 1000) => { + return await HttpLogic.values.http.get('/api/ml/trained_models', { + query: { size, with_pipelines: true }, + }); +}; + +export const MLModelsApiLogic = createApiLogic(['ml_models_api_logic'], getMLModels); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/fetch_ml_inference_pipeline_processors.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/fetch_ml_inference_pipeline_processors.ts new file mode 100644 index 0000000000000..85f481b513525 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/fetch_ml_inference_pipeline_processors.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 { InferencePipeline } from '../../../../../common/types/pipelines'; +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export const fetchMlInferencePipelineProcessors = async ({ indexName }: { indexName: string }) => { + const route = `/internal/enterprise_search/indices/${indexName}/ml_inference/pipeline_processors`; + + return await HttpLogic.values.http.get(route); +}; + +export const FetchMlInferencePipelineProcessorsApiLogic = createApiLogic( + ['fetch_ml_inference_pipeline_processors_api_logic'], + fetchMlInferencePipelineProcessors +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel.scss b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel.scss new file mode 100644 index 0000000000000..0f571379476eb --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel.scss @@ -0,0 +1,5 @@ +.authenticationPanel { + .authenticationCheckable { + height: 100%; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel.tsx new file mode 100644 index 0000000000000..ddfa88cf27ff0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel.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 { useValues } from 'kea'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { DataPanel } from '../../../../shared/data_panel/data_panel'; + +import { AuthenticationPanelActions } from './authentication_panel_actions'; +import { AuthenticationPanelDeleteConfirmationModal } from './authentication_panel_delete_confirmation_modal'; +import { AuthenticationPanelEditContent } from './authentication_panel_edit_content'; +import { AuthenticationPanelLogic } from './authentication_panel_logic'; +import { AuthenticationPanelViewContent } from './authentication_panel_view_content'; + +import './authentication_panel.scss'; + +export const AuthenticationPanel: React.FC = () => { + const { isEditing, isModalVisible } = useValues(AuthenticationPanelLogic); + + return ( + <> + + {i18n.translate('xpack.enterpriseSearch.crawler.authenticationPanel.title', { + defaultMessage: 'Authentication', + })} + + } + action={} + subtitle={ + + } + > + {isEditing ? : } + + {isModalVisible && } + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_actions.tsx new file mode 100644 index 0000000000000..2a42a26ee8754 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_actions.tsx @@ -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 React from 'react'; + +import { useActions, useValues } from 'kea'; + +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { + SAVE_BUTTON_LABEL, + CANCEL_BUTTON_LABEL, + DELETE_BUTTON_LABEL, +} from '../../../../shared/constants'; +import { CrawlerAuth } from '../../../api/crawler/types'; +import { CrawlerDomainDetailLogic } from '../crawler_domain_detail_logic'; + +import { AuthenticationPanelLogic } from './authentication_panel_logic'; + +export const AuthenticationPanelActions: React.FC = () => { + const { domain } = useValues(CrawlerDomainDetailLogic); + + const currentAuth: CrawlerAuth = domain?.auth ?? null; + + const { disableEditing, enableEditing, saveCredentials, setIsModalVisible } = + useActions(AuthenticationPanelLogic); + + const { isEditing } = useValues(AuthenticationPanelLogic); + + return isEditing ? ( + + + saveCredentials()} + > + {SAVE_BUTTON_LABEL} + + + + disableEditing()} + > + {CANCEL_BUTTON_LABEL} + + + + ) : currentAuth === null ? ( + enableEditing(currentAuth)} + > + {i18n.translate( + 'xpack.enterpriseSearch.crawler.authenticationPanel.resetToDefaultsButtonLabel', + { + defaultMessage: 'Add credentials', + } + )} + + ) : ( + { + setIsModalVisible(true); + }} + > + {DELETE_BUTTON_LABEL} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_delete_confirmation_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_delete_confirmation_modal.tsx new file mode 100644 index 0000000000000..98c9737801887 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_delete_confirmation_modal.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 } from 'kea'; + +import { EuiConfirmModal } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { CANCEL_BUTTON_LABEL } from '../../../../shared/constants'; + +import { AuthenticationPanelLogic } from './authentication_panel_logic'; + +export const AuthenticationPanelDeleteConfirmationModal: React.FC = () => { + const { deleteCredentials, setIsModalVisible } = useActions(AuthenticationPanelLogic); + + return ( + { + event?.preventDefault(); + setIsModalVisible(false); + }} + onConfirm={(event) => { + event.preventDefault(); + deleteCredentials(); + }} + cancelButtonText={CANCEL_BUTTON_LABEL} + confirmButtonText={i18n.translate( + 'xpack.enterpriseSearch.crawler.authenticationPanel.deleteConfirmationModal.deleteButtonLabel', + { + defaultMessage: 'Delete', + } + )} + defaultFocusedButton="confirm" + buttonColor="danger" + > + {i18n.translate( + 'xpack.enterpriseSearch.crawler.authenticationPanel.deleteConfirmationModal.description', + { + defaultMessage: + 'Deleting these settings might prevent the crawler from indexing protected areas of the domain. This can not be undone.', + } + )} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx new file mode 100644 index 0000000000000..eeed5bb377fe7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { + EuiForm, + EuiFieldText, + EuiFieldPassword, + EuiFormRow, + EuiTitle, + EuiCheckableCard, + EuiFormFieldset, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; + +import { USERNAME_LABEL, PASSWORD_LABEL, TOKEN_LABEL } from '../../../../shared/constants'; + +import { AuthenticationPanelLogic } from './authentication_panel_logic'; +import { AUTHENTICATION_LABELS } from './constants'; + +export const AuthenticationPanelEditContent: React.FC = () => { + const { selectAuthOption, setHeaderContent, setPassword, setUsername } = + useActions(AuthenticationPanelLogic); + + const { headerContent, username, password, selectedAuthOption } = + useValues(AuthenticationPanelLogic); + + return ( + + + + +
{AUTHENTICATION_LABELS.basic}
+ + } + value="basic" + checked={selectedAuthOption === 'basic'} + onChange={() => selectAuthOption('basic')} + > + + + setUsername(event.target.value)} + disabled={selectedAuthOption !== 'basic'} + /> + + + setPassword(event.target.value)} + disabled={selectedAuthOption !== 'basic'} + /> + + +
+
+ + +
{AUTHENTICATION_LABELS.raw}
+ + } + value="raw" + checked={selectedAuthOption === 'raw'} + onChange={() => selectAuthOption('raw')} + > + + + setHeaderContent(event.target.value)} + disabled={selectedAuthOption !== 'raw'} + /> + + +
+
+
+
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_logic.ts new file mode 100644 index 0000000000000..de78802ca2ee0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_logic.ts @@ -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 { kea, MakeLogicType } from 'kea'; + +import { CrawlerAuth } from '../../../api/crawler/types'; +import { isRawCrawlerAuth, isBasicCrawlerAuth } from '../../../api/crawler/utils'; +import { + CrawlerDomainDetailActions, + CrawlerDomainDetailLogic, +} from '../crawler_domain_detail_logic'; + +interface AuthenticationPanelValues { + headerContent: string; + isEditing: boolean; + isModalVisible: boolean; + password: string; + selectedAuthOption: string | null; + username: string; +} + +type AuthenticationPanelActions = { + deleteCredentials(): void; + disableEditing(): void; + enableEditing(currentCrawlerAuth?: CrawlerAuth): { currentCrawlerAuth: CrawlerAuth | undefined }; + saveCredentials(): void; + selectAuthOption(authType: string): { authType: string }; + setHeaderContent(headerContent: string): { headerContent: string }; + setIsModalVisible(isModalVisible: boolean): { isModalVisible: boolean }; + setPassword(password: string): { password: string }; + setUsername(username: string): { username: string }; +} & Pick; + +export const AuthenticationPanelLogic = kea< + MakeLogicType +>({ + path: ['enterprise_search', 'app_search', 'crawler', 'authentication_panel'], + connect: { + actions: [CrawlerDomainDetailLogic, ['submitAuthUpdate', 'receiveDomainData']], + }, + actions: () => ({ + deleteCredentials: true, + disableEditing: true, + enableEditing: (currentCrawlerAuth) => ({ currentCrawlerAuth }), + saveCredentials: true, + selectAuthOption: (authType) => ({ authType }), + setHeaderContent: (headerContent) => ({ headerContent }), + setIsModalVisible: (isModalVisible) => ({ isModalVisible }), + setPassword: (password) => ({ password }), + setUsername: (username) => ({ username }), + }), + reducers: () => ({ + headerContent: [ + '', + { + enableEditing: (_, { currentCrawlerAuth }) => + currentCrawlerAuth !== undefined && isRawCrawlerAuth(currentCrawlerAuth) + ? currentCrawlerAuth.header + : '', + receiveDomainData: () => '', + setHeaderContent: (_, { headerContent }) => headerContent, + }, + ], + isEditing: [ + false, + { + disableEditing: () => false, + enableEditing: () => true, + receiveDomainData: () => false, + }, + ], + isModalVisible: [ + false, + { + receiveDomainData: () => false, + setIsModalVisible: (_, { isModalVisible }) => isModalVisible, + }, + ], + password: [ + '', + { + enableEditing: (_, { currentCrawlerAuth }) => + currentCrawlerAuth !== undefined && isBasicCrawlerAuth(currentCrawlerAuth) + ? currentCrawlerAuth.password + : '', + receiveDomainData: () => '', + setPassword: (_, { password }) => password, + }, + ], + selectedAuthOption: [ + null, + { + enableEditing: (_, { currentCrawlerAuth }) => currentCrawlerAuth?.type ?? 'basic', + receiveDomainData: () => null, + selectAuthOption: (_, { authType }) => authType, + }, + ], + username: [ + '', + { + enableEditing: (_, { currentCrawlerAuth }) => + currentCrawlerAuth !== undefined && isBasicCrawlerAuth(currentCrawlerAuth) + ? currentCrawlerAuth.username + : '', + receiveDomainData: () => '', + setUsername: (_, { username }) => username, + }, + ], + }), + listeners: ({ values }) => ({ + saveCredentials: () => { + const { headerContent, password, selectedAuthOption, username } = values; + if (selectedAuthOption === 'basic') { + CrawlerDomainDetailLogic.actions.submitAuthUpdate({ + password, + type: 'basic', + username, + }); + } else if (selectedAuthOption === 'raw') { + CrawlerDomainDetailLogic.actions.submitAuthUpdate({ + header: headerContent, + type: 'raw', + }); + } + }, + deleteCredentials: () => { + CrawlerDomainDetailLogic.actions.submitAuthUpdate(null); + }, + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_view_content.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_view_content.tsx new file mode 100644 index 0000000000000..34d29d05dfe7b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_view_content.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useValues } from 'kea'; + +import { EuiEmptyPrompt, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { CrawlerDomainDetailLogic } from '../crawler_domain_detail_logic'; + +import './authentication_panel.scss'; + +export const AuthenticationPanelViewContent: React.FC = () => { + const { domain } = useValues(CrawlerDomainDetailLogic); + + return domain?.auth ? ( + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.crawler.authenticationPanel.configurationSavePanel.title', + { + defaultMessage: 'Configuration settings saved', + } + )} +

+
+ + + {i18n.translate( + 'xpack.enterpriseSearch.crawler.authenticationPanel.configurationSavePanel.description', + { + defaultMessage: + 'Authentication settings for crawling protected content have been saved. To update an authentication mechanism, delete settings and restart.', + } + )} + +
+ ) : ( + + {i18n.translate('xpack.enterpriseSearch.crawler.authenticationPanel.emptyPrompt.title', { + defaultMessage: 'No authentication configured', + })} + + } + body={ + + {i18n.translate( + 'xpack.enterpriseSearch.crawler.authenticationPanel.emptyPrompt.addAuthenticationButtonLabel', + { + defaultMessage: 'Add authentication', + } + )} + + ), + }} + /> + } + titleSize="s" + /> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/constants.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/constants.ts new file mode 100644 index 0000000000000..2918f54892cc5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/constants.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 { i18n } from '@kbn/i18n'; + +export const AUTHENTICATION_LABELS = { + basic: i18n.translate( + 'xpack.enterpriseSearch.crawler.authenticationPanel.basicAuthenticationLabel', + { + defaultMessage: 'Basic authentication', + } + ), + raw: i18n.translate('xpack.enterpriseSearch.crawler.authenticationPanel.rawAuthenticationLabel', { + defaultMessage: 'Bearer authentication', + }), +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/crawler_domain_detail.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/crawler_domain_detail.tsx index e655e71066bf1..793da41b89be1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/crawler_domain_detail.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/crawler_domain_detail.tsx @@ -28,6 +28,7 @@ import { SearchIndexTabId } from '../search_index/search_index'; import { baseBreadcrumbs } from '../search_indices'; import { CrawlerStatusIndicator } from '../shared/crawler_status_indicator/crawler_status_indicator'; +import { AuthenticationPanel } from './authentication_panel/authentication_panel'; import { CrawlRulesTable } from './crawl_rules_table'; import { CrawlerDomainDetailLogic } from './crawler_domain_detail_logic'; import { DeduplicationPanel } from './deduplication_panel/deduplication_panel'; @@ -96,6 +97,8 @@ export const CrawlerDomainDetail: React.FC = () => { + + diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/crawler_domain_detail_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/crawler_domain_detail_logic.ts index c2ae2e3409eac..0b00622492c9b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/crawler_domain_detail_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/crawler_domain_detail_logic.ts @@ -21,6 +21,7 @@ import { DeleteCrawlerDomainResponse, } from '../../api/crawler/delete_crawler_domain_api_logic'; import { + CrawlerAuth, CrawlerDomain, CrawlerDomainFromServer, CrawlRule, @@ -44,13 +45,14 @@ export interface CrawlerDomainDetailValues { getLoading: boolean; } -interface CrawlerDomainDetailActions { +export interface CrawlerDomainDetailActions { deleteApiError(error: HttpError): HttpError; deleteApiSuccess(response: DeleteCrawlerDomainResponse): DeleteCrawlerDomainResponse; deleteDomain(): void; deleteMakeRequest(args: DeleteCrawlerDomainArgs): DeleteCrawlerDomainArgs; fetchDomainData(domainId: string): { domainId: string }; receiveDomainData(domain: CrawlerDomain): { domain: CrawlerDomain }; + submitAuthUpdate(auth: CrawlerAuth): { auth: CrawlerAuth }; submitDeduplicationUpdate(payload: { enabled?: boolean; fields?: string[] }): { enabled: boolean; fields: string[]; @@ -80,6 +82,7 @@ export const CrawlerDomainDetailLogic = kea< deleteDomainComplete: () => true, fetchDomainData: (domainId) => ({ domainId }), receiveDomainData: (domain) => ({ domain }), + submitAuthUpdate: (auth) => ({ auth }), submitDeduplicationUpdate: ({ fields, enabled }) => ({ enabled, fields }), updateCrawlRules: (crawlRules) => ({ crawlRules }), updateEntryPoints: (entryPoints) => ({ entryPoints }), @@ -135,7 +138,6 @@ export const CrawlerDomainDetailLogic = kea< deleteApiError: (error) => { flashAPIErrors(error); }, - fetchDomainData: async ({ domainId }) => { const { http } = HttpLogic.values; const { indexName } = IndexNameLogic.values; @@ -152,6 +154,30 @@ export const CrawlerDomainDetailLogic = kea< flashAPIErrors(e); } }, + submitAuthUpdate: async ({ auth }) => { + const { http } = HttpLogic.values; + const { indexName } = IndexNameLogic.values; + const { domainId } = values; + + const payload = { + auth, + }; + + try { + const response = await http.put( + `/internal/enterprise_search/indices/${indexName}/crawler/domains/${domainId}`, + { + body: JSON.stringify(payload), + } + ); + + const domainData = crawlerDomainServerToClient(response); + + actions.receiveDomainData(domainData); + } catch (e) { + flashAPIErrors(e); + } + }, submitDeduplicationUpdate: async ({ fields, enabled }) => { const { http } = HttpLogic.values; const { indexName } = IndexNameLogic.values; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/entry_points_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/entry_points_table.test.tsx index e57cd195946a5..a540a24188783 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/entry_points_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/entry_points_table.test.tsx @@ -25,6 +25,7 @@ describe('EntryPointsTable', () => { { id: '2', value: '/foo' }, ]; const domain: CrawlerDomain = { + auth: null, availableDeduplicationFields: ['title', 'description'], crawlRules: [], createdOn: '2018-01-01T00:00:00.000Z', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/sitemaps_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/sitemaps_table.test.tsx index ded26ca4b1acf..f8d1ddd3d4b57 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/sitemaps_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/sitemaps_table.test.tsx @@ -16,6 +16,8 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { GenericEndpointInlineEditableTable } from '../../../shared/tables/generic_endpoint_inline_editable_table'; +import { CrawlerDomain } from '../../api/crawler/types'; + import { SitemapsTable } from './sitemaps_table'; describe('SitemapsTable', () => { @@ -25,7 +27,8 @@ describe('SitemapsTable', () => { { id: '1', url: 'http://www.example.com/sitemap.xml' }, { id: '2', url: 'http://www.example.com/whatever/sitemaps.xml' }, ]; - const domain = { + const domain: CrawlerDomain = { + auth: null, createdOn: '2018-01-01T00:00:00.000Z', documentCount: 10, id: '6113e1407a2f2e6f42489794', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx index 70cf83fbb9764..47b5d565e62d4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/method_connector.tsx @@ -60,12 +60,21 @@ export const MethodConnector: React.FC<{ isNative: boolean }> = ({ isNative }) = docsUrl="https://github.com/elastic/connectors-ruby/blob/main/README.md" disabled={isGated} error={errorToText(error)} - title={i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.steps.buildConnector.title', - { - defaultMessage: 'Build a connector', - } - )} + title={ + isNative + ? i18n.translate( + 'xpack.enterpriseSearch.content.newIndex.steps.nativeConnector.title', + { + defaultMessage: 'Index using a connector', + } + ) + : i18n.translate( + 'xpack.enterpriseSearch.content.newIndex.steps.buildConnector.title', + { + defaultMessage: 'Build a connector', + } + ) + } type="connector" onNameChange={() => { apiReset(); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/curl_request/curl_request.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/curl_request/curl_request.tsx new file mode 100644 index 0000000000000..4e99b6e8eb780 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/curl_request/curl_request.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 from 'react'; + +import { EuiCodeBlock } from '@elastic/eui'; + +import { IngestPipelineParams } from '../../../../../../../common/types/connectors'; +import { useCloudDetails } from '../../../../../shared/cloud_details/cloud_details'; + +import { decodeCloudId } from '../../../../utils/decode_cloud_id'; + +interface CurlRequestParams { + apiKey?: string; + document?: Record; + indexName: string; + pipeline?: IngestPipelineParams; +} + +export const CurlRequest: React.FC = ({ + indexName, + apiKey, + document, + pipeline, +}) => { + const cloudContext = useCloudDetails(); + + const DEFAULT_URL = 'https://localhost:9200'; + const baseUrl = + (cloudContext.cloudId && decodeCloudId(cloudContext.cloudId)?.elasticsearchUrl) || DEFAULT_URL; + const apiKeyExample = apiKey || ''; + const { name: pipelineName, ...pipelineParams } = pipeline ?? {}; + // We have to prefix the parameters with an underscore because that's what the actual pipeline looks for + const pipelineArgs = Object.entries(pipelineParams).reduce( + (acc: Record, curr) => ({ ...acc, [`_${curr[0]}`]: curr[1] }), + {} + ); + + const inputDocument = pipeline ? { ...document, ...pipelineArgs } : document; + + return ( + + {`\ +curl -X POST '${baseUrl}/${indexName}/_doc${pipeline ? `?pipeline=${pipelineName}` : ''}' \\ + -H 'Content-Type: application/json' \\ + -H 'Authorization: ApiKey ${apiKeyExample}' \\ + -d '${JSON.stringify(inputDocument, null, 2)}' +`} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx index b4cbc1e63d024..1ef66afa3106f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx @@ -43,6 +43,7 @@ import { SearchIndexTabId } from '../search_index'; import { ApiKeyConfig } from './api_key_configuration'; import { ConnectorConfigurationConfig } from './connector_configuration_config'; +import { NativeConnectorConfiguration } from './native_connector_configuration/native_connector_configuration'; export const ConnectorConfiguration: React.FC = () => { const { data: apiKeyData } = useValues(GenerateConnectorApiKeyApiLogic); @@ -53,6 +54,10 @@ export const ConnectorConfiguration: React.FC = () => { return <>; } + if (indexData.connector.is_native && indexData.connector.service_type) { + return ; + } + const hasApiKey = !!(indexData.connector.api_key_id ?? apiKeyData); return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_config.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_config.tsx index 5588a9c16fd5d..85118fe0c5005 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_config.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_config.tsx @@ -9,24 +9,14 @@ import React from 'react'; import { useActions, useValues } from 'kea'; -import { - EuiButton, - EuiCallOut, - EuiDescriptionList, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiSpacer, - EuiText, -} from '@elastic/eui'; +import { EuiButton, EuiDescriptionList, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import { ConnectorConfigurationForm } from './connector_configuration_form'; import { ConnectorConfigurationLogic } from './connector_configuration_logic'; -export const ConnectorConfigurationConfig: React.FC = () => { +export const ConnectorConfigurationConfig: React.FC = ({ children }) => { const { configView, isEditing } = useValues(ConnectorConfigurationLogic); const { setIsEditing } = useActions(ConnectorConfigurationLogic); @@ -37,76 +27,7 @@ export const ConnectorConfigurationConfig: React.FC = () => { return ( - - - - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.config.connectorClientLink', - { defaultMessage: 'example connector client' } - )} - - ), - }} - /> - -

- {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.config.description.secondParagraph', - { - defaultMessage: - 'While the connector clients in the repository are built in Ruby, there’s no technical limitation to only use Ruby. Build a connector client with the technology that works best for your skillset.', - } - )} -

- - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.config.discussLink', - { defaultMessage: 'Discuss' } - )} - - ), - issuesLink: ( - - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.config.issuesLink', - { defaultMessage: 'issue' } - )} - - ), - }} - /> - - - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.warning.description', - { - defaultMessage: - 'If you sync at least one document before you’ve finalized your connector client, you will have to recreate your search index.', - } - )} - -
-
+ {children && {children}} {isEditing ? ( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts index b16891be1366b..243546374b5ca 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration_logic.ts @@ -89,13 +89,15 @@ export const ConnectorConfigurationLogic = kea< }), listeners: ({ actions, values }) => ({ apiError: (error) => flashAPIErrors(error), - apiSuccess: () => + apiSuccess: ({ indexName }) => { flashSuccessToast( i18n.translate( 'xpack.enterpriseSearch.content.indices.configurationConnector.configuration.successToast.title', { defaultMessage: 'Configuration successfully updated' } ) - ), + ); + FetchIndexApiLogic.actions.makeRequest({ indexName }); + }, fetchIndexApiSuccess: (index) => { if (!values.isEditing && isConnectorIndex(index)) { actions.setConfigState(index.connector.configuration); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_overview_panels.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_overview_panels.tsx index ee56437a7aead..a0d1c3d97fe96 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_overview_panels.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_overview_panels.tsx @@ -28,6 +28,8 @@ import { import { IndexViewLogic } from '../index_view_logic'; import { SearchIndexTabId } from '../search_index'; +import { NATIVE_CONNECTORS } from './constants'; + const StatusPanel: React.FC<{ ingestionStatus: IngestionStatus }> = ({ ingestionStatus }) => ( { } )} title={ + NATIVE_CONNECTORS.find( + (connector) => connector.serviceType === index.connector.service_type + )?.name ?? index.connector.service_type ?? i18n.translate('xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label', { defaultMessage: 'Unknown', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts new file mode 100644 index 0000000000000..7cb12fc5f2eda --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +import { NativeConnector } from './types'; + +export const NATIVE_CONNECTORS: NativeConnector[] = [ + { + configuration: { + host: { + value: '', + label: i18n.translate( + 'xpack.enterpriseSearch.content.nativeConnectors.mongodb.configuration.hostLabel', + { + defaultMessage: 'MongoDB host', + } + ), + }, + user: { + value: '', + label: i18n.translate( + 'xpack.enterpriseSearch.content.nativeConnectors.mongodb.configuration.usernameLabel', + { + defaultMessage: 'MongoDB username', + } + ), + }, + password: { + value: '', + label: i18n.translate( + 'xpack.enterpriseSearch.content.nativeConnectors.mongodb.configuration.passwordLabel', + { + defaultMessage: 'MongoDB password', + } + ), + }, + database: { + value: '', + label: i18n.translate( + 'xpack.enterpriseSearch.content.nativeConnectors.mongodb.configuration.databaseLabel', + { + defaultMessage: 'MongoDB database', + } + ), + }, + collection: { + value: '', + label: i18n.translate( + 'xpack.enterpriseSearch.content.nativeConnectors.mongodb.configuration.collectionLabel', + { + defaultMessage: 'MongoDB collection', + } + ), + }, + }, + name: i18n.translate('xpack.enterpriseSearch.content.nativeConnectors.mongodb.name', { + defaultMessage: 'MongoDB', + }), + serviceType: 'mongodb', + }, + { + configuration: { + host: { + value: '', + label: i18n.translate( + 'xpack.enterpriseSearch.content.nativeConnectors.mysql.configuration.hostLabel', + { + defaultMessage: 'MySQL host', + } + ), + }, + port: { + value: '', + label: i18n.translate( + 'xpack.enterpriseSearch.content.nativeConnectors.mysql.configuration.portLabel', + { + defaultMessage: 'MySQL port', + } + ), + }, + user: { + value: '', + label: i18n.translate( + 'xpack.enterpriseSearch.content.nativeConnectors.mysql.configuration.usernameLabel', + { + defaultMessage: 'MySQL username', + } + ), + }, + password: { + value: '', + label: i18n.translate( + 'xpack.enterpriseSearch.content.nativeConnectors.mysql.configuration.passwordLabel', + { + defaultMessage: 'MySQL password', + } + ), + }, + database: { + value: '', + label: i18n.translate( + 'xpack.enterpriseSearch.content.nativeConnectors.mysql.configuration.databasesLabel', + { + defaultMessage: 'List of MySQL databases', + } + ), + }, + }, + name: i18n.translate('xpack.enterpriseSearch.content.nativeConnectors.mysql.name', { + defaultMessage: 'MySQL', + }), + serviceType: 'mysql', + }, +]; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/custom_connector_configuration_config.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/custom_connector_configuration_config.tsx new file mode 100644 index 0000000000000..b1ca63a453952 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/custom_connector_configuration_config.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export const ConnectorConfigurationConfig: React.FC = () => { + return ( + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.connectorClientLink', + { defaultMessage: 'example connector client' } + )} + + ), + }} + /> + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.description.secondParagraph', + { + defaultMessage: + 'While the connector clients in the repository are built in Ruby, there’s no technical limitation to only use Ruby. Build a connector client with the technology that works best for your skillset.', + } + )} +

+ + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.discussLink', + { defaultMessage: 'Discuss' } + )} + + ), + issuesLink: ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.config.issuesLink', + { defaultMessage: 'issue' } + )} + + ), + }} + /> + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.warning.description', + { + defaultMessage: + 'If you sync at least one document before you’ve finalized your connector client, you will have to recreate your search index.', + } + )} + +
+
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/connector_name_and_description.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/connector_name_and_description.tsx new file mode 100644 index 0000000000000..552d0ce197c87 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/connector_name_and_description.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useValues } from 'kea'; + +import { + EuiButton, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiText, + EuiTextArea, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { isConnectorIndex } from '../../../../utils/indices'; +import { IndexViewLogic } from '../../index_view_logic'; +import { NativeConnector } from '../types'; + +interface ConnectorNameAndDescriptionProps { + nativeConnector: NativeConnector; +} + +export const ConnectorNameAndDescription: React.FC = ({ + nativeConnector, +}) => { + const { index: indexData } = useValues(IndexViewLogic); + const { name } = nativeConnector; + return ( + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionForm.description', + { + defaultMessage: + 'By naming and describing this connector your colleagues and wider team will know what this connector is meant for.', + } + )} + + + + + + + + + + + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionForm.submitButtonLabel', + { + defaultMessage: 'Save name and description', + } + )} + + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx new file mode 100644 index 0000000000000..fba38e958163a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useValues } from 'kea'; + +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import { generateEncodedPath } from '../../../../../shared/encode_path_params'; +import { EuiLinkTo, EuiButtonTo } from '../../../../../shared/react_router_helpers'; + +import { SEARCH_INDEX_TAB_PATH } from '../../../../routes'; +import { IndexNameLogic } from '../../index_name_logic'; +import { SearchIndexTabId } from '../../search_index'; + +export const NativeConnectorAdvancedConfiguration: React.FC = () => { + const { indexName } = useValues(IndexNameLogic); + + return ( + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.recurringScheduleLinkLabel', + { + defaultMessage: 'recurring sync schedule', + } + )} + + ), + }} + /> + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.schedulingButtonLabel', + { + defaultMessage: 'Set schedule and sync', + } + )} + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx new file mode 100644 index 0000000000000..24b410db6544f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.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 React from 'react'; + +import { useValues } from 'kea'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiPanel, + EuiSpacer, + EuiSteps, + EuiTitle, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { NATIVE_CONNECTOR_ICONS } from '../../../../../../assets/source_icons/native_connector_icons'; + +import { hasConfiguredConfiguration } from '../../../../utils/has_configured_configuration'; +import { isConnectorIndex } from '../../../../utils/indices'; +import { IndexViewLogic } from '../../index_view_logic'; +import { NATIVE_CONNECTORS } from '../constants'; + +import { NativeConnectorAdvancedConfiguration } from './native_connector_advanced_configuration'; +import { NativeConnectorConfigurationConfig } from './native_connector_configuration_config'; +import { ResearchConfiguration } from './research_configuration'; + +export const NativeConnectorConfiguration: React.FC = () => { + const { index: indexData } = useValues(IndexViewLogic); + + if (!isConnectorIndex(indexData)) { + return <>; + } + + const nativeConnector = NATIVE_CONNECTORS.find( + (connector) => connector.serviceType === indexData.connector.service_type + ); + + if (!nativeConnector) { + return <>; + } + + const hasConfigured = hasConfiguredConfiguration(indexData.connector.configuration); + const hasConfiguredAdvanced = + indexData.connector.last_synced || indexData.connector.scheduling.enabled; + const hasResearched = hasConfigured || hasConfiguredAdvanced; + + const icon = NATIVE_CONNECTOR_ICONS[nativeConnector.serviceType]; + return ( + <> + + + + + + {icon && ( + + + + )} + + +

{nativeConnector.name}

+
+
+
+ + , + status: hasResearched ? 'complete' : 'incomplete', + title: i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.researchConfigurationTitle', + { + defaultMessage: 'Research configuration requirements', + } + ), + titleSize: 'xs', + }, + /* Commenting this out for a future PR to implement fully */ + // { + // children: , + // status: hasName ? 'complete' : 'incomplete', + // title: i18n.translate( + // 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.nameAndDescriptionTitle', + // { + // defaultMessage: 'Name and description', + // } + // ), + // titleSize: 'xs', + // }, + { + children: ( + + ), + status: hasConfigured ? 'complete' : 'incomplete', + title: i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.configurationTitle', + { + defaultMessage: 'Configuration', + } + ), + titleSize: 'xs', + }, + { + children: , + status: hasConfiguredAdvanced ? 'complete' : 'incomplete', + title: i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.advancedConfigurationTitle', + { + defaultMessage: 'Advanced configuration', + } + ), + titleSize: 'xs', + }, + ]} + /> +
+
+ + + +
+ + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration_config.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration_config.tsx new file mode 100644 index 0000000000000..9836f344c314c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration_config.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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, EuiLink, EuiText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { ConnectorConfigurationConfig } from '../connector_configuration_config'; +import { NativeConnector } from '../types'; + +interface NativeConnectorConfigurationConfigProps { + nativeConnector: NativeConnector; +} + +export const NativeConnectorConfigurationConfig: React.FC< + NativeConnectorConfigurationConfigProps +> = ({ nativeConnector }) => { + return ( + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.encryptionWarningMessage', + { + defaultMessage: + 'Encryption for data source credentials is unavailable in this technical preview. Your data source credentials will be stored, unencrypted, in Elasticsearch.', + } + )} + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.securityDocumentationLinkLabel', + { + defaultMessage: 'Learn more about Elasticsearch security', + } + )} + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.sourceSecurityDocumentationLinkLabel', + { + defaultMessage: '{name} authentication', + values: { + name: nativeConnector.name, + }, + } + )} + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/research_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/research_configuration.tsx new file mode 100644 index 0000000000000..7f264f53cfb12 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/research_configuration.tsx @@ -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 React from 'react'; + +import { EuiText, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { NativeConnector } from '../types'; + +interface ResearchConfigurationProps { + nativeConnector: NativeConnector; +} +export const ResearchConfiguration: React.FC = ({ + nativeConnector, +}) => { + const { name } = nativeConnector; + + return ( + <> + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.description', + { + defaultMessage: + '{name} supports a variety of authentication mechanisms which will be needed for this connector to connect to your instance. Consult with your administrator for the correct credentials to use to connect.', + values: { + name, + }, + } + )} + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.connectorDocumentationLinkLabel', + { + defaultMessage: 'Documentation', + } + )} + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.researchConfiguration.serviceDocumentationLinkLabel', + { + defaultMessage: '{name} documentation', + values: { name }, + } + )} + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/select_connector/connector_checkable.scss b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/select_connector/connector_checkable.scss new file mode 100644 index 0000000000000..e8f65d5846e91 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/select_connector/connector_checkable.scss @@ -0,0 +1,9 @@ +.connectorCheckable { + .euiRadio { + margin-top: 4px; + } + + .connectorCheckableContent { + margin-left: 20px; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/select_connector/connector_checkable.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/select_connector/connector_checkable.tsx new file mode 100644 index 0000000000000..4841c14348575 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/select_connector/connector_checkable.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { + EuiCheckableCard, + EuiCheckableCardProps, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { NATIVE_CONNECTOR_ICONS } from '../../../../../../assets/source_icons/native_connector_icons'; + +import './connector_checkable.scss'; + +export type ConnectorCheckableProps = Omit< + EuiCheckableCardProps, + 'id' | 'label' | 'name' | 'value' +> & { + documentationUrl: string; + name: string; + serviceType: string; +}; + +export const ConnectorCheckable: React.FC = ({ + documentationUrl, + name, + serviceType, + ...props +}) => { + const icon = NATIVE_CONNECTOR_ICONS[serviceType]; + return ( + + {icon && ( + + + + )} + + + {name} + + +
+ } + name={name} + value={serviceType} + > +
+ + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.selectConnector.connectorCheckable.description', + { + defaultMessage: 'Search over your {name} content with Enterprise Search.', + values: { name }, + } + )} + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.selectConnector.connectorCheckable.basicAuthenticationLabel', + { + defaultMessage: 'Basic authentication', + } + )} + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.selectConnector.connectorCheckable.documentationLinkLabel', + { + defaultMessage: 'Documentation', + } + )} + +
+ + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/select_connector/select_connector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/select_connector/select_connector.tsx new file mode 100644 index 0000000000000..1031e0fa80013 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/select_connector/select_connector.tsx @@ -0,0 +1,178 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { useActions, useValues } from 'kea'; + +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiFormFieldset, + EuiLink, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import { Status } from '../../../../../../../common/types/api'; +import { generateEncodedPath } from '../../../../../shared/encode_path_params'; + +import { flashSuccessToast } from '../../../../../shared/flash_messages'; +import { KibanaLogic } from '../../../../../shared/kibana'; +import { FetchIndexApiLogic } from '../../../../api/index/fetch_index_api_logic'; +import { SEARCH_INDEX_TAB_PATH } from '../../../../routes'; +import { isConnectorIndex } from '../../../../utils/indices'; +import { EnterpriseSearchContentPageTemplate } from '../../../layout'; +import { baseBreadcrumbs } from '../../../search_indices'; +import { IndexNameLogic } from '../../index_name_logic'; + +import { NATIVE_CONNECTORS } from '../constants'; + +import { ConnectorCheckable } from './connector_checkable'; +import { SelectConnectorLogic } from './select_connector_logic'; + +export const SelectConnector: React.FC = () => { + const { data: indexData, status: indexApiStatus } = useValues(FetchIndexApiLogic); + const { selectedNativeConnector } = useValues(SelectConnectorLogic); + const { saveNativeConnector, setSelectedConnector } = useActions(SelectConnectorLogic); + + const { indexName } = useValues(IndexNameLogic); + + useEffect(() => { + if (isConnectorIndex(indexData) && indexData.connector.service_type) { + flashSuccessToast( + i18n.translate( + 'xpack.enterpriseSearch.content.indices.selectConnector.successToast.title', + { + defaultMessage: 'Your index will now use the {connectorName} native connector.', + values: { + connectorName: NATIVE_CONNECTORS.find( + (connector) => connector.serviceType === indexData.connector.service_type + )?.name, + }, + } + ) + ); + KibanaLogic.values.navigateToUrl( + generateEncodedPath(SEARCH_INDEX_TAB_PATH, { + indexName, + tabId: 'configuration', + }) + ); + } + }, [indexData]); + + return ( + +
{ + event.preventDefault(); + saveNativeConnector(); + }} + > + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.selectConnector.title', + { + defaultMessage: 'Select a connector', + } + )} + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.indices.selectConnector.description', + { + defaultMessage: + "Get started by selecting the connector you'd like to configure to extract, index and sync data from your data source into your newly created search index.", + } + )} +

+
+ + ), + }} + > + + + {NATIVE_CONNECTORS.map((nativeConnector) => ( + + setSelectedConnector(nativeConnector)} + documentationUrl={'' /* TODO docLinks */} + checked={nativeConnector === selectedNativeConnector} + /> + + ))} + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.selectConnector.selectAndConfigureButtonLabel', + { + defaultMessage: 'Select and configure', + } + )} + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.selectConnector.buildYourOwnConnectorLinkLabel', + { + defaultMessage: 'build your own', + } + )} + + ), + workplaceSearchLink: ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.selectConnector.workplaceSearchLinkLabel', + { + defaultMessage: 'View additional integrations in Workplace Search', + } + )} + + ), + }} + /> + +
+
+
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/select_connector/select_connector_logic.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/select_connector/select_connector_logic.tsx new file mode 100644 index 0000000000000..d7243ecfe4001 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/select_connector/select_connector_logic.tsx @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { Actions } from '../../../../../shared/api_logic/create_api_logic'; +import { generateEncodedPath } from '../../../../../shared/encode_path_params'; +import { clearFlashMessages, flashAPIErrors } from '../../../../../shared/flash_messages'; + +import { KibanaLogic } from '../../../../../shared/kibana'; +import { + SetNativeConnectorArgs, + SetNativeConnectorLogic, + SetNativeConnectorResponse, +} from '../../../../api/connector/set_native_connector_api_logic'; + +import { + FetchIndexApiLogic, + FetchIndexApiResponse, +} from '../../../../api/index/fetch_index_api_logic'; + +import { SEARCH_INDEX_TAB_PATH } from '../../../../routes'; +import { isConnectorIndex } from '../../../../utils/indices'; +import { NATIVE_CONNECTORS } from '../constants'; +import { NativeConnector } from '../types'; + +type SelectConnectorActions = Pick< + Actions, + 'apiError' | 'apiSuccess' | 'makeRequest' +> & { + saveNativeConnector(): void; + setSelectedConnector(nativeConnector: NativeConnector): { + nativeConnector: NativeConnector; + }; +}; + +interface SelectConnectorValues { + index: FetchIndexApiResponse; + selectedNativeConnector: NativeConnector | null; +} + +export const SelectConnectorLogic = kea< + MakeLogicType +>({ + actions: { + saveNativeConnector: true, + setSelectedConnector: (nativeConnector) => ({ nativeConnector }), + }, + connect: { + actions: [SetNativeConnectorLogic, ['apiError', 'apiSuccess', 'makeRequest']], + values: [FetchIndexApiLogic, ['data as index']], + }, + events: ({ actions, values }) => ({ + afterMount: () => { + if (isConnectorIndex(values.index)) { + const serviceType = values.index.connector.service_type; + const nativeConnector = NATIVE_CONNECTORS.find( + (connector) => connector.serviceType === serviceType + ); + if (nativeConnector) { + actions.setSelectedConnector(nativeConnector); + } + } + }, + }), + listeners: ({ actions, values }) => ({ + apiError: (error) => flashAPIErrors(error), + apiSuccess: () => { + FetchIndexApiLogic.actions.makeRequest({ indexName: values.index.name }); + }, + makeRequest: () => clearFlashMessages(), + saveNativeConnector: () => { + if (!isConnectorIndex(values.index) || values.selectedNativeConnector === null) { + KibanaLogic.values.navigateToUrl( + generateEncodedPath(SEARCH_INDEX_TAB_PATH, { + indexName: values.index.name, + tabId: 'configuration', + }) + ); + } else { + actions.makeRequest({ + connectorId: values.index.connector.id, + nativeConnector: values.selectedNativeConnector, + }); + } + }, + }), + path: ['enterprise_search', 'content', 'select_connector'], + reducers: () => ({ + selectedNativeConnector: [ + null, + { + setSelectedConnector: (_, { nativeConnector }) => nativeConnector, + }, + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/types.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/types.ts new file mode 100644 index 0000000000000..e905f3d2e3634 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/types.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ConnectorConfiguration } from '../../../../../../common/types/connectors'; + +export interface NativeConnector { + name: string; + serviceType: string; + configuration: ConnectorConfiguration; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_logic.ts index 7b664de1e24cc..5b9c9dc5c8945 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_logic.ts @@ -9,9 +9,11 @@ import { kea, MakeLogicType } from 'kea'; import { i18n } from '@kbn/i18n'; +import { ElasticsearchIndexWithIngestion } from '../../../../../../common/types/indices'; import { Actions } from '../../../../shared/api_logic/create_api_logic'; import { flashAPIErrors, flashSuccessToast } from '../../../../shared/flash_messages'; import { HttpLogic } from '../../../../shared/http'; +import { StartSyncApiLogic, StartSyncArgs } from '../../../api/connector/start_sync_api_logic'; import { GetCrawlerApiLogic, GetCrawlerArgs } from '../../../api/crawler/get_crawler_api_logic'; import { CrawlerData, @@ -20,6 +22,14 @@ import { CrawlEvent, CrawlRequest, } from '../../../api/crawler/types'; + +import { + FetchIndexApiLogic, + FetchIndexApiParams, + FetchIndexApiResponse, +} from '../../../api/index/fetch_index_api_logic'; + +import { isCrawlerIndex } from '../../../utils/indices'; import { IndexNameLogic } from '../index_name_logic'; export interface CrawlRequestOverrides { @@ -38,8 +48,13 @@ export interface CrawlerValues { mostRecentCrawlRequest: CrawlRequest | null; mostRecentCrawlRequestStatus: CrawlerStatus | null; timeoutId: NodeJS.Timeout | null; + connectorId: string | null; + indexData?: ElasticsearchIndexWithIngestion; } +type FetchIndexApiValues = Actions; +type StartSyncApiValues = Actions; + export type CrawlerActions = Pick< Actions, 'apiError' | 'apiSuccess' @@ -50,6 +65,8 @@ export type CrawlerActions = Pick< reApplyCrawlRules(domain?: CrawlerDomain): { domain?: CrawlerDomain }; startCrawl(overrides?: CrawlRequestOverrides): { overrides?: CrawlRequestOverrides }; stopCrawl(): void; + makeStartSyncRequest: StartSyncApiValues['makeRequest']; + makeFetchIndexRequest: FetchIndexApiValues['makeRequest']; }; export const CrawlerLogic = kea>({ @@ -60,10 +77,17 @@ export const CrawlerLogic = kea>({ stopCrawl: () => null, }, connect: { - actions: [GetCrawlerApiLogic, ['apiError', 'apiSuccess']], - values: [GetCrawlerApiLogic, ['status', 'data']], + actions: [ + GetCrawlerApiLogic, + ['apiError', 'apiSuccess'], + StartSyncApiLogic, + ['makeRequest as makeStartSyncRequest'], + FetchIndexApiLogic, + ['makeRequest as makeFetchIndexRequest'], + ], + values: [GetCrawlerApiLogic, ['status', 'data'], FetchIndexApiLogic, ['data as indexData']], }, - listeners: ({ actions }) => ({ + listeners: ({ actions, values }) => ({ apiError: (error) => { flashAPIErrors(error); }, @@ -101,14 +125,15 @@ export const CrawlerLogic = kea>({ } }, startCrawl: async ({ overrides = {} }) => { - const { indexName } = IndexNameLogic.values; - const { http } = HttpLogic.values; - try { - await http.post(`/internal/enterprise_search/indices/${indexName}/crawler/crawl_requests`, { - body: JSON.stringify({ overrides }), - }); - actions.fetchCrawlerData(); + if (isCrawlerIndex(values.indexData) && values.indexData.connector) { + actions.makeStartSyncRequest({ + connectorId: values.indexData.connector.id, + nextSyncConfig: overrides, + }); + + actions.fetchCrawlerData(); + } } catch (e) { flashAPIErrors(e); } 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 1edac9de49f53..6ef7512a59483 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 @@ -38,6 +38,7 @@ const domains: CrawlerDomain[] = [ deduplicationEnabled: false, deduplicationFields: ['title'], availableDeduplicationFields: ['title', 'description'], + auth: null, }, { id: '4567', @@ -50,6 +51,7 @@ const domains: CrawlerDomain[] = [ deduplicationEnabled: false, deduplicationFields: ['title'], availableDeduplicationFields: ['title', 'description'], + auth: null, }, ]; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/generate_api_key_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/generate_api_key_panel.tsx index 6a43cc82d568d..5a9c32c625fdc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/generate_api_key_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/generate_api_key_panel.tsx @@ -5,48 +5,38 @@ * 2.0. */ -import React from 'react'; +import React, { useState } from 'react'; import { useActions, useValues } from 'kea'; -import { - EuiCodeBlock, - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiSwitch, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useCloudDetails } from '../../../shared/cloud_details/cloud_details'; -import { decodeCloudId } from '../../utils/decode_cloud_id'; - import { DOCUMENTS_API_JSON_EXAMPLE } from '../new_index/constants'; +import { SettingsLogic } from '../settings/settings_logic'; + import { ClientLibrariesPopover } from './components/client_libraries_popover/popover'; +import { CurlRequest } from './components/curl_request/curl_request'; import { GenerateApiKeyModal } from './components/generate_api_key_modal/modal'; import { ManageKeysPopover } from './components/manage_api_keys_popover/popover'; +import { IndexViewLogic } from './index_view_logic'; import { OverviewLogic } from './overview.logic'; export const GenerateApiKeyPanel: React.FC = () => { - const { apiKey, isGenerateModalOpen, indexData } = useValues(OverviewLogic); + const { apiKey, isGenerateModalOpen } = useValues(OverviewLogic); + const { indexName } = useValues(IndexViewLogic); const { closeGenerateModal } = useActions(OverviewLogic); + const { defaultPipeline } = useValues(SettingsLogic); - const cloudContext = useCloudDetails(); - - const DEFAULT_URL = 'https://localhost:9200'; - const searchIndexApiUrl = - (cloudContext.cloudId && decodeCloudId(cloudContext.cloudId)?.elasticsearchUrl) || DEFAULT_URL; - - const apiKeyExample = apiKey || ''; + const [optimizedRequest, setOptimizedRequest] = useState(true); return ( <> {isGenerateModalOpen && ( - + )} @@ -55,7 +45,7 @@ export const GenerateApiKeyPanel: React.FC = () => { - {indexData?.name[0] !== '.' && ( + {indexName[0] !== '.' && (

{i18n.translate( @@ -66,6 +56,16 @@ export const GenerateApiKeyPanel: React.FC = () => { )} + + setOptimizedRequest(event.target.checked)} + label={i18n.translate( + 'xpack.enterpriseSearch.content.overview.optimizedRequest.label', + { defaultMessage: 'View Enterprise Search optimized request' } + )} + checked={optimizedRequest} + /> + @@ -78,18 +78,16 @@ export const GenerateApiKeyPanel: React.FC = () => { - {indexData?.name[0] !== '.' && ( + {indexName[0] !== '.' && ( <> - - {`\ -curl -X POST '${searchIndexApiUrl}/${indexData?.name}/_doc' \\ - -H 'Content-Type: application/json' \\ - -H 'Authorization: ApiKey ${apiKeyExample}' \\ - -d '${JSON.stringify(DOCUMENTS_API_JSON_EXAMPLE, null, 2)}' -`} - + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts index e49745dacfcb7..454bc5f9a0be0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts @@ -9,7 +9,11 @@ import { kea, MakeLogicType } from 'kea'; import { i18n } from '@kbn/i18n'; -import { SyncStatus } from '../../../../../common/types/connectors'; +import { + Connector, + IngestPipelineParams, + SyncStatus, +} from '../../../../../common/types/connectors'; import { Actions } from '../../../shared/api_logic/create_api_logic'; import { flashAPIErrors, @@ -62,6 +66,7 @@ export interface IndexViewActions { } export interface IndexViewValues { + connector: Connector | undefined; connectorId: string | null; data: typeof FetchIndexApiLogic.values.data; fetchIndexTimeoutId: NodeJS.Timeout | null; @@ -73,6 +78,7 @@ export interface IndexViewValues { isWaitingForSync: boolean; lastUpdated: string | null; localSyncNowValue: boolean; // holds local value after update so UI updates correctly + pipelineData: IngestPipelineParams | undefined; recheckIndexLoading: boolean; resetFetchIndexLoading: boolean; syncStatus: SyncStatus | null; @@ -217,6 +223,13 @@ export const IndexViewLogic = kea ({ + connector: [ + () => [selectors.index], + (index: ElasticsearchViewIndex | undefined) => + index && (isConnectorViewIndex(index) || isCrawlerIndex(index)) + ? index.connector + : undefined, + ], connectorId: [ () => [selectors.index], (index) => (isConnectorViewIndex(index) ? index.connector.id : null), @@ -233,6 +246,10 @@ export const IndexViewLogic = kea data?.connector?.sync_now || localSyncNowValue, ], lastUpdated: [() => [selectors.data], (data) => getLastUpdated(data)], + pipelineData: [ + () => [selectors.connector], + (connector: Connector | undefined) => connector?.pipeline ?? undefined, + ], syncStatus: [() => [selectors.data], (data) => data?.connector?.last_sync_status ?? null], }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/custom_pipeline_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/custom_pipeline_panel.tsx new file mode 100644 index 0000000000000..702ecf89c9580 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/custom_pipeline_panel.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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, EuiTitle, EuiText, EuiBadge } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { EuiButtonEmptyTo } from '../../../../shared/react_router_helpers'; + +export const CustomPipelinePanel: React.FC<{ + indexName: string; + pipelineSuffix: string; + processorsCount: number; +}> = ({ indexName, pipelineSuffix, processorsCount }) => { + return ( + + + + + +

{`${indexName}@${pipelineSuffix}`}

+
+
+ + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.ingestPipelinesCard.customButtonLabel', + { defaultMessage: 'Edit pipeline' } + )} + + +
+
+ + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.ingestPipelinesCard.customDescription', + { + defaultMessage: 'Custom ingest pipeline for {indexName}', + values: { indexName }, + } + )} + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.ingestPipelinesCard.processorsDescription', + { + defaultMessage: '{processorsCount} Processors', + values: { processorsCount }, + } + )} + + + + +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipeline_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipeline_modal.tsx new file mode 100644 index 0000000000000..b60da157ebf1f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipeline_modal.tsx @@ -0,0 +1,236 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiLink, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { DEFAULT_PIPELINE_NAME } from '../../../../../../common/constants'; + +import { IngestPipelineParams } from '../../../../../../common/types/connectors'; + +import { CurlRequest } from '../components/curl_request/curl_request'; + +import { PipelineSettingsForm } from './pipeline_settings_form'; + +interface IngestPipelineModalProps { + closeModal: () => void; + createCustomPipelines: () => void; + displayOnly: boolean; + indexName: string; + isGated: boolean; + isLoading: boolean; + pipeline: IngestPipelineParams; + savePipeline: () => void; + setPipeline: (pipeline: IngestPipelineParams) => void; + showModal: boolean; +} + +export const IngestPipelineModal: React.FC = ({ + closeModal, + createCustomPipelines, + displayOnly, + indexName, + isGated, + isLoading, + pipeline, + savePipeline, + setPipeline, + showModal, +}) => { + const { name } = pipeline; + + // can't customize if you already have a custom pipeline! + const canCustomize = name === DEFAULT_PIPELINE_NAME; + + return showModal ? ( + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.modalHeaderTitle', + { + defaultMessage: 'Pipeline settings', + } + )} + + + + + {name} + + + + + + + + + + + {displayOnly + ? i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.modalBodyAPIText', + { + defaultMessage: + 'This pipeline runs automatically on all Crawler and Connector indices created through Enterprise Search. To use this configuration on API-based indices you can use the sample cURL request below.', + } + ) + : i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.modalBodyConnectorText', + { + defaultMessage: + 'This pipeline runs automatically on all Crawler and Connector indices created through Enterprise Search.', + } + )} + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.modalIngestLinkLabel', + { + defaultMessage: 'Learn more about Enterprise Search ingest pipelines', + } + )} + + + + + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.settings.formHeader', + { + defaultMessage: 'Optimize your content for search', + } + )} + + + + + + + + + + {displayOnly && ( + <> + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.curlHeader', + { + defaultMessage: 'Sample cURL request', + } + )} + + + + + + + )} + {canCustomize && ( + <> + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.platinumText', + { + defaultMessage: + 'With a platinum license, you can create an index-specific version of this configuration and modify it for your use case.', + } + )} + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.copyButtonLabel', + { defaultMessage: 'Copy and customize' } + )} + + + + + )} + + + + {displayOnly ? ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.closeButtonLabel', + { + defaultMessage: 'Close', + } + )} + + ) : ( + <> + + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + )} + + + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.saveButtonLabel', + { + defaultMessage: 'Save', + } + )} + + + )} + + + ) : ( + <> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx new file mode 100644 index 0000000000000..ca6140b7b9625 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiButtonEmpty, + EuiAccordion, + EuiBadge, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { KibanaLogic } from '../../../../shared/kibana'; + +import { LicensingLogic } from '../../../../shared/licensing'; +import { CreateCustomPipelineApiLogic } from '../../../api/index/create_custom_pipeline_api_logic'; +import { FetchCustomPipelineApiLogic } from '../../../api/index/fetch_custom_pipeline_api_logic'; +import { CurlRequest } from '../components/curl_request/curl_request'; +import { IndexViewLogic } from '../index_view_logic'; + +import { CustomPipelinePanel } from './custom_pipeline_panel'; +import { IngestPipelineModal } from './ingest_pipeline_modal'; +import { PipelinesLogic } from './pipelines_logic'; + +export const IngestPipelinesCard: React.FC = () => { + const { indexName } = useValues(IndexViewLogic); + + const { canSetPipeline, pipelineState, showModal } = useValues(PipelinesLogic); + const { closeModal, openModal, setPipelineState, savePipeline } = useActions(PipelinesLogic); + const { makeRequest: fetchCustomPipeline } = useActions(FetchCustomPipelineApiLogic); + const { makeRequest: createCustomPipeline } = useActions(CreateCustomPipelineApiLogic); + const { data: customPipelines } = useValues(FetchCustomPipelineApiLogic); + const { isCloud } = useValues(KibanaLogic); + const { hasPlatinumLicense } = useValues(LicensingLogic); + + const isGated = !isCloud && !hasPlatinumLicense; + const customPipeline = customPipelines ? customPipelines[`${indexName}@custom`] : undefined; + + useEffect(() => { + fetchCustomPipeline({ indexName }); + }, [indexName]); + + return ( + + createCustomPipeline({ indexName })} + displayOnly={!canSetPipeline} + indexName={indexName} + isGated={isGated} + isLoading={false} + pipeline={pipelineState} + savePipeline={savePipeline} + setPipeline={setPipelineState} + showModal={showModal} + /> + {customPipeline && ( + + + + + + )} + + + + + + + +

{pipelineState.name}

+
+
+ + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.ingestPipelinesCard.settings.label', + { defaultMessage: 'Settings' } + )} + + +
+
+ + + + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.ingestPipelinesCard.managedBadge.label', + { defaultMessage: 'Managed' } + )} + + + + +
+
+
+
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/add_ml_inference_button.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/add_ml_inference_button.tsx new file mode 100644 index 0000000000000..6a11c17e878aa --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/add_ml_inference_button.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; + +import { useValues } from 'kea'; + +import { EuiButton, EuiToolTip } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { KibanaLogic } from '../../../../../shared/kibana/kibana_logic'; +import { PipelinesLogic } from '../pipelines_logic'; + +export interface AddMLInferencePipelineButtonProps { + onClick: () => void; +} +export const AddMLInferencePipelineButton: React.FC = ({ + onClick, +}) => { + const { capabilities } = useValues(KibanaLogic); + const { canUseMlInferencePipeline } = useValues(PipelinesLogic); + const hasMLPermissions = capabilities?.ml?.canAccessML ?? false; + if (!hasMLPermissions) { + return ( + + + + ); + } + if (!canUseMlInferencePipeline) { + return ( + + + + ); + } + return ; +}; + +const AddButton: React.FC<{ disabled?: boolean; onClick?: () => void }> = ({ + disabled, + onClick, +}) => ( + + {i18n.translate('xpack.enterpriseSearch.content.indices.pipelines.mlInference.addButtonLabel', { + defaultMessage: 'Add inference pipeline', + })} + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/add_ml_inference_pipeline_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/add_ml_inference_pipeline_modal.tsx new file mode 100644 index 0000000000000..8b2cfb1d4b5d8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/add_ml_inference_pipeline_modal.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; + +import { useValues, useActions } from 'kea'; + +import { + EuiButton, + EuiButtonEmpty, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiLoadingSpinner, + EuiSpacer, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { IndexNameLogic } from '../../index_name_logic'; + +import { ConfigurePipeline } from './configure_pipeline'; +import { MLInferenceLogic, AddInferencePipelineModal } from './ml_inference_logic'; +import { NoModelsPanel } from './no_models'; + +interface AddMLInferencePipelineModalProps { + onClose: () => void; +} + +export const AddMLInferencePipelineModal: React.FC = ({ + onClose, +}) => { + const { indexName } = useValues(IndexNameLogic); + const { setIndexName } = useActions(MLInferenceLogic); + useEffect(() => { + setIndexName(indexName); + }, [indexName]); + + return ( + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.title', + { + defaultMessage: 'Add an inference pipeline', + } + )} +

+
+
+ +
+ ); +}; + +const isProcessorConfigurationInvalid = ({ configuration }: AddInferencePipelineModal): boolean => { + const { pipelineName, modelID, sourceField } = configuration; + return pipelineName.trim().length === 0 || modelID.length === 0 || sourceField.length === 0; +}; + +const AddProcessorContent: React.FC = ({ onClose }) => { + const { addInferencePipelineModal, createErrors, supportedMLModels, isLoading } = + useValues(MLInferenceLogic); + const { createPipeline } = useActions(MLInferenceLogic); + if (isLoading) { + return ( + + + + ); + } + if (supportedMLModels === undefined || supportedMLModels?.length === 0) { + return ; + } + return ( + <> + + {createErrors.length > 0 && ( + <> + + {createErrors.map((message, i) => ( +

{message}

+ ))} +
+ + + )} + +
+ + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.footer.cancel', + { + defaultMessage: 'Cancel', + } + )} + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.footer.create', + { + defaultMessage: 'Create', + } + )} + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx new file mode 100644 index 0000000000000..da95bd6d081f4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx @@ -0,0 +1,207 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useValues, useActions } from 'kea'; + +import { + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiLink, + EuiSelect, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { MLInferenceLogic } from './ml_inference_logic'; + +export const ConfigurePipeline: React.FC = () => { + const { + addInferencePipelineModal: { configuration }, + formErrors, + supportedMLModels, + sourceFields, + } = useValues(MLInferenceLogic); + const { setInferencePipelineConfiguration } = useActions(MLInferenceLogic); + + const { destinationField, modelID, pipelineName, sourceField } = configuration; + const models = supportedMLModels ?? []; + + return ( + <> + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.description', + { + defaultMessage: + "Once created, this pipeline will be added as a processor on your Enterprise Search Ingestion Pipeline. You'll also be able to use this pipeline elsewhere in your Elastic deployment.", + } + )} +

+ + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.docsLink', + { + defaultMessage: 'Learn more about using ML models in Enterprise Search', + } + )} + +
+ + + + + setInferencePipelineConfiguration({ + ...configuration, + pipelineName: e.target.value, + }) + } + /> + + + + ({ text: m.model_id, value: m.model_id })), + ]} + onChange={(e) => + setInferencePipelineConfiguration({ + ...configuration, + modelID: e.target.value, + }) + } + /> + + + + + + ({ + text: field, + value: field, + })) ?? []), + ]} + onChange={(e) => + setInferencePipelineConfiguration({ + ...configuration, + sourceField: e.target.value, + }) + } + /> + + + + + + setInferencePipelineConfiguration({ + ...configuration, + destinationField: e.target.value, + }) + } + /> + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts new file mode 100644 index 0000000000000..eb8f7f4f0e660 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts @@ -0,0 +1,211 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types'; + +import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; + +import { HttpError, Status } from '../../../../../../../common/types/api'; + +import { generateEncodedPath } from '../../../../../shared/encode_path_params'; +import { getErrorsFromHttpResponse } from '../../../../../shared/flash_messages/handle_api_errors'; +import { KibanaLogic } from '../../../../../shared/kibana'; +import { MappingsApiLogic } from '../../../../api/mappings/mappings_logic'; +import { CreateMlInferencePipelineApiLogic } from '../../../../api/ml_models/create_ml_inference_pipeline'; +import { MLModelsApiLogic } from '../../../../api/ml_models/ml_models_logic'; + +import { SEARCH_INDEX_TAB_PATH } from '../../../../routes'; +import { SearchIndexTabId } from '../../search_index'; + +import { AddInferencePipelineFormErrors, InferencePipelineConfiguration } from './types'; +import { + isSupportedMLModel, + sortSourceFields, + validateInferencePipelineConfiguration, +} from './utils'; + +export const EMPTY_PIPELINE_CONFIGURATION: InferencePipelineConfiguration = { + destinationField: '', + modelID: '', + pipelineName: '', + sourceField: '', +}; + +const API_REQUEST_COMPLETE_STATUSES = [Status.SUCCESS, Status.ERROR]; + +interface MLInferenceProcessorsActions { + clearFormErrors: () => void; + createApiError: (error: HttpError) => HttpError; + createApiSuccess: typeof CreateMlInferencePipelineApiLogic.actions.apiSuccess; + createPipeline: () => void; + makeCreatePipelineRequest: typeof CreateMlInferencePipelineApiLogic.actions.makeRequest; + makeMLModelsRequest: typeof MLModelsApiLogic.actions.makeRequest; + makeMappingRequest: typeof MappingsApiLogic.actions.makeRequest; + mappingsApiError(error: HttpError): HttpError; + mlModelsApiError(error: HttpError): HttpError; + setCreateErrors(errors: string[]): { errors: string[] }; + setFormErrors: (inputErrors: AddInferencePipelineFormErrors) => { + inputErrors: AddInferencePipelineFormErrors; + }; + setIndexName: (indexName: string) => { indexName: string }; + setInferencePipelineConfiguration: (configuration: InferencePipelineConfiguration) => { + configuration: InferencePipelineConfiguration; + }; +} + +export interface AddInferencePipelineModal { + configuration: InferencePipelineConfiguration; + indexName: string; +} + +interface MLInferenceProcessorsValues { + addInferencePipelineModal: AddInferencePipelineModal; + createErrors: string[]; + formErrors: AddInferencePipelineFormErrors; + isLoading: boolean; + mappingData: typeof MappingsApiLogic.values.data; + mappingStatus: Status; + mlModelsData: typeof MLModelsApiLogic.values.data; + mlModelsStatus: typeof MLModelsApiLogic.values.apiStatus; + sourceFields: string[] | undefined; + supportedMLModels: typeof MLModelsApiLogic.values.data; +} + +export const MLInferenceLogic = kea< + MakeLogicType +>({ + actions: { + clearFormErrors: true, + createPipeline: true, + setCreateErrors: (errors: string[]) => ({ errors }), + setFormErrors: (inputErrors: AddInferencePipelineFormErrors) => ({ inputErrors }), + setIndexName: (indexName: string) => ({ indexName }), + setInferencePipelineConfiguration: (configuration: InferencePipelineConfiguration) => ({ + configuration, + }), + }, + connect: { + actions: [ + MappingsApiLogic, + ['makeRequest as makeMappingRequest', 'apiError as mappingsApiError'], + MLModelsApiLogic, + ['makeRequest as makeMLModelsRequest', 'apiError as mlModelsApiError'], + CreateMlInferencePipelineApiLogic, + [ + 'apiError as createApiError', + 'apiSuccess as createApiSuccess', + 'makeRequest as makeCreatePipelineRequest', + ], + ], + values: [ + MappingsApiLogic, + ['data as mappingData', 'status as mappingStatus'], + MLModelsApiLogic, + ['data as mlModelsData', 'status as mlModelsStatus'], + ], + }, + events: {}, + listeners: ({ values, actions }) => ({ + createApiSuccess: () => { + KibanaLogic.values.navigateToUrl( + generateEncodedPath(SEARCH_INDEX_TAB_PATH, { + indexName: values.addInferencePipelineModal.indexName, + tabId: SearchIndexTabId.PIPELINES, + }) + ); + }, + createPipeline: () => { + const { + addInferencePipelineModal: { configuration, indexName }, + } = values; + const validationErrors = validateInferencePipelineConfiguration(configuration); + if (validationErrors !== undefined) { + actions.setFormErrors(validationErrors); + return; + } + actions.clearFormErrors(); + + actions.makeCreatePipelineRequest({ + indexName, + pipelineName: configuration.pipelineName, + modelId: configuration.modelID, + sourceField: configuration.sourceField, + destinationField: + configuration.destinationField.trim().length > 0 + ? configuration.destinationField + : undefined, + }); + }, + makeCreatePipelineRequest: () => actions.setCreateErrors([]), + setIndexName: ({ indexName }) => { + actions.makeMLModelsRequest(undefined); + actions.makeMappingRequest({ indexName }); + }, + }), + reducers: { + addInferencePipelineModal: [ + { + configuration: { + ...EMPTY_PIPELINE_CONFIGURATION, + }, + indexName: '', + }, + { + setIndexName: (modal, { indexName }) => ({ ...modal, indexName }), + setInferencePipelineConfiguration: (modal, { configuration }) => ({ + ...modal, + configuration, + }), + }, + ], + createErrors: [ + [], + { + createApiError: (_, error) => getErrorsFromHttpResponse(error), + setCreateErrors: (_, { errors }) => errors, + }, + ], + formErrors: [ + {}, + { + clearFormErrors: () => ({}), + setFormErrors: (_, { inputErrors }) => inputErrors, + }, + ], + }, + selectors: ({ selectors }) => ({ + isLoading: [ + () => [selectors.mlModelsStatus, selectors.mappingStatus], + (mlModelsStatus, mappingStatus) => + !API_REQUEST_COMPLETE_STATUSES.includes(mlModelsStatus) || + !API_REQUEST_COMPLETE_STATUSES.includes(mappingStatus), + ], + sourceFields: [ + () => [selectors.mappingStatus, selectors.mappingData], + (status: Status, mapping: IndicesGetMappingIndexMappingRecord) => { + if (status !== Status.SUCCESS) return; + if (mapping?.mappings?.properties === undefined) return []; + return Object.entries(mapping.mappings.properties) + .reduce((fields, [key, value]) => { + if (value.type === 'text' || value.type === 'keyword') { + fields.push(key); + } + return fields; + }, [] as string[]) + .sort(sortSourceFields); + }, + ], + supportedMLModels: [ + () => [selectors.mlModelsData], + (mlModelsData: TrainedModelConfigResponse[] | undefined) => { + return mlModelsData?.filter(isSupportedMLModel); + }, + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.tsx new file mode 100644 index 0000000000000..084fb4244cb7a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.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 React from 'react'; + +import { EuiEmptyPrompt, EuiImage, EuiLink, EuiText, useEuiTheme } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import noMlModelsGraphicDark from '../../../../../../assets/images/no_ml_models_dark.svg'; +import noMlModelsGraphicLight from '../../../../../../assets/images/no_ml_models_light.svg'; + +export const NoModelsPanel: React.FC = () => { + const { colorMode } = useEuiTheme(); + + return ( + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.noModels.description', + { + defaultMessage: + 'You have no trained models available. Please follow the documenation to add trained ml models to your cluster.', + } + )} +

+
+ + } + footer={ + // TODO: insert correct docsLink here + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.emptyPrompt.docsLink', + { + defaultMessage: 'Learn More', + } + )} + + } + /> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/types.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/types.ts new file mode 100644 index 0000000000000..9ada53d224c8e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/types.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. + */ + +export interface InferencePipelineConfiguration { + destinationField: string; + modelID: string; + pipelineName: string; + sourceField: string; +} + +export interface AddInferencePipelineFormErrors { + destinationField?: string; + pipelineName?: string; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.test.ts new file mode 100644 index 0000000000000..068886f3c4ff7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.test.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; + +import { isSupportedMLModel, isValidPipelineName, sortSourceFields } from './utils'; + +describe('ml inference utils', () => { + describe('isSupportedMLModel', () => { + const makeFakeModel = ( + config: Partial + ): TrainedModelConfigResponse => ({ + inference_config: {}, + input: { + field_names: [], + }, + model_id: 'a-model-001', + model_type: 'pytorch', + tags: [], + version: '1', + ...config, + }); + it('returns true for expected models', () => { + const models: TrainedModelConfigResponse[] = [ + makeFakeModel({ + inference_config: { + ner: {}, + }, + }), + makeFakeModel({ + inference_config: { + classification: {}, + }, + }), + makeFakeModel({ + inference_config: { + text_classification: {}, + }, + }), + makeFakeModel({ + inference_config: { + text_embedding: {}, + }, + }), + makeFakeModel({ + inference_config: { + zero_shot_classification: { + classification_labels: [], + }, + }, + }), + ]; + + for (const model of models) { + expect(isSupportedMLModel(model)).toBe(true); + } + }); + + it('returns false for expected models', () => { + const models: TrainedModelConfigResponse[] = [makeFakeModel({})]; + + for (const model of models) { + expect(isSupportedMLModel(model)).toBe(false); + } + }); + }); + describe('sortSourceFields', () => { + it('promotes fields', () => { + let fields: string[] = ['id', 'body', 'url']; + expect(fields.sort(sortSourceFields)).toEqual(['body', 'id', 'url']); + fields = ['id', 'body_content', 'url']; + expect(fields.sort(sortSourceFields)).toEqual(['body_content', 'id', 'url']); + fields = ['id', 'title', 'message', 'url']; + expect(fields.sort(sortSourceFields)).toEqual(['title', 'id', 'message', 'url']); + fields = ['id', 'body', 'title', 'message', 'url']; + expect(fields.sort(sortSourceFields)).toEqual(['body', 'title', 'id', 'message', 'url']); + }); + }); + describe('isValidPipelineName', () => { + it('allows alphanumeric characters, underscores, & hypens', () => { + expect(isValidPipelineName('apipelinename123')).toEqual(true); + expect(isValidPipelineName('a_pipeline_name123')).toEqual(true); + expect(isValidPipelineName('a-pipeline-name-123')).toEqual(true); + }); + it('does not allow special characters', () => { + expect(isValidPipelineName('a_pipeline_name_1$')).toEqual(false); + expect(isValidPipelineName('a_pipeline_name_1%')).toEqual(false); + expect(isValidPipelineName('a_pipeline_name_1^')).toEqual(false); + expect(isValidPipelineName('a_pipeline_name_1"')).toEqual(false); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.ts new file mode 100644 index 0000000000000..8a3ef72c72af7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.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. + */ + +import { i18n } from '@kbn/i18n'; +import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; + +import { AddInferencePipelineFormErrors, InferencePipelineConfiguration } from './types'; + +const NLP_CONFIG_KEYS = [ + 'ner', + 'classification', + 'text_classification', + 'text_embedding', + 'zero_shot_classification', +]; +export const isSupportedMLModel = (model: TrainedModelConfigResponse): boolean => { + return Object.keys(model.inference_config).some((key) => NLP_CONFIG_KEYS.includes(key)); +}; + +const RECOMMENDED_FIELDS = ['body', 'body_content', 'title']; +export const sortSourceFields = (a: string, b: string): number => { + const promoteA = RECOMMENDED_FIELDS.includes(a); + const promoteB = RECOMMENDED_FIELDS.includes(b); + if (promoteA && promoteB) { + return RECOMMENDED_FIELDS.indexOf(a) > RECOMMENDED_FIELDS.indexOf(b) ? 1 : -1; + } else if (promoteA) { + return -1; + } else if (promoteB) { + return 1; + } + return a.localeCompare(b); +}; + +const VALID_PIPELINE_NAME_REGEX = /^[\w\s\-]+$/; +export const isValidPipelineName = (input: string): boolean => { + return input.length > 0 && VALID_PIPELINE_NAME_REGEX.test(input); +}; + +export const validateInferencePipelineConfiguration = ( + config: InferencePipelineConfiguration +): AddInferencePipelineFormErrors | undefined => { + const errors: AddInferencePipelineFormErrors = {}; + if (!isValidPipelineName(config.pipelineName)) { + errors.pipelineName = i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.invalidPipelineName', + { + defaultMessage: 'Name must only contain letters, numbers, underscores, and hyphens.', + } + ); + return errors; + } +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx new file mode 100644 index 0000000000000..0b8d7ec671753 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference_pipeline_processors_card.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { InferencePipeline } from '../../../../../../common/types/pipelines'; +import { IndexNameLogic } from '../index_name_logic'; + +import { InferencePipelineCard } from './inference_pipeline_card'; +import { PipelinesLogic } from './pipelines_logic'; + +export const MlInferencePipelineProcessorsCard: React.FC = () => { + const { indexName } = useValues(IndexNameLogic); + const { mlInferencePipelineProcessors: inferencePipelines } = useValues(PipelinesLogic); + const { fetchMlInferenceProcessors } = useActions(PipelinesLogic); + useEffect(() => { + fetchMlInferenceProcessors({ indexName }); + }, [indexName]); + + if (inferencePipelines === undefined) return null; + if (inferencePipelines.length === 0) return null; + + return ( + + {inferencePipelines.map((item: InferencePipeline, index: number) => ( + + + + ))} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipeline_settings_form.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipeline_settings_form.tsx new file mode 100644 index 0000000000000..c01ca3d11a45a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipeline_settings_form.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 React from 'react'; + +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { IngestPipelineParams } from '../../../../../../common/types/connectors'; +import { SettingsCheckableCard } from '../../shared/settings_checkable_card/settings_checkable_card'; + +interface PipelineSettingsFormProps { + pipeline: IngestPipelineParams; + setPipeline: (pipeline: IngestPipelineParams) => void; +} + +export const PipelineSettingsForm: React.FC = ({ + setPipeline, + pipeline, +}) => { + const { + extract_binary_content: extractBinaryContent, + reduce_whitespace: reduceWhitespace, + run_ml_inference: runMLInference, + } = pipeline; + return ( + + + + setPipeline({ + ...pipeline, + extract_binary_content: !pipeline.extract_binary_content, + }) + } + checked={extractBinaryContent} + id="ingestPipelineExtractBinaryContent" + /> + + + + setPipeline({ ...pipeline, reduce_whitespace: !pipeline.reduce_whitespace }) + } + /> + + + + setPipeline({ ...pipeline, run_ml_inference: !pipeline.run_ml_inference }) + } + /> + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx index c2246348efe1c..e523d6060da56 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx @@ -7,38 +7,20 @@ import React from 'react'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { InferencePipeline } from '../../../../../../common/types/pipelines'; import { DataPanel } from '../../../../shared/data_panel/data_panel'; -import { InferencePipelineCard } from './inference_pipeline_card'; +import { IngestPipelinesCard } from './ingest_pipelines_card'; +import { AddMLInferencePipelineButton } from './ml_inference/add_ml_inference_button'; +import { AddMLInferencePipelineModal } from './ml_inference/add_ml_inference_pipeline_modal'; +import { MlInferencePipelineProcessorsCard } from './ml_inference_pipeline_processors_card'; export const SearchIndexPipelines: React.FC = () => { - // TODO: REPLACE THIS DATA WITH REAL DATA - - const inferencePipelines: InferencePipeline[] = [ - { - pipelineName: 'NER Processor', - trainedModelName: 'elastic_dslim_bert_base_ner', - isDeployed: true, - modelType: 'pytorch', - }, - { - pipelineName: 'Sentiment Analysis', - trainedModelName: 'elastic_dslim_bert_base_ner', - isDeployed: false, - modelType: 'pytorch', - }, - { - pipelineName: 'Sentiment Analysis', - trainedModelName: 'elastic_dslim_bert_base_ner', - isDeployed: false, - modelType: 'pytorch', - }, - ]; + const [addInferencePipelineOpen, setShowAddInferencePipeline] = React.useState(false); + const closeAddInferencePipelineModal = () => setShowAddInferencePipeline(false); return ( <> @@ -75,7 +57,7 @@ export const SearchIndexPipelines: React.FC = () => { )} iconType="logstashInput" > -
+ @@ -110,33 +92,16 @@ export const SearchIndexPipelines: React.FC = () => { )} iconType="compute" action={ - - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.pipelines.mlInferencePipelines.newButton', - { - defaultMessage: 'Add ML inference pipeline', - } - )} - + setShowAddInferencePipeline(true)} /> } > - {inferencePipelines.length > 0 && ( - - {inferencePipelines.map((item: InferencePipeline, index: number) => ( - - - - ))} - - )} + + {addInferencePipelineOpen && ( + + )} ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts new file mode 100644 index 0000000000000..45f3126c19f96 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts @@ -0,0 +1,187 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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, mockFlashMessageHelpers } from '../../../../__mocks__/kea_logic'; +import { connectorIndex } from '../../../__mocks__/view_index.mock'; + +import { UpdatePipelineApiLogic } from '../../../api/connector/update_pipeline_api_logic'; +import { FetchIndexApiLogic } from '../../../api/index/fetch_index_api_logic'; + +import { PipelinesLogic } from './pipelines_logic'; + +const DEFAULT_PIPELINE_VALUES = { + extract_binary_content: true, + name: 'ent-search-generic-ingestion', + reduce_whitespace: true, + run_ml_inference: false, +}; + +const DEFAULT_VALUES = { + canSetPipeline: true, + canUseMlInferencePipeline: false, + defaultPipelineValues: DEFAULT_PIPELINE_VALUES, + defaultPipelineValuesData: undefined, + index: undefined, + mlInferencePipelineProcessors: undefined, + pipelineState: DEFAULT_PIPELINE_VALUES, + showModal: false, +}; + +describe('PipelinesLogic', () => { + const { mount } = new LogicMounter(PipelinesLogic); + const { mount: mountFetchIndexApiLogic } = new LogicMounter(FetchIndexApiLogic); + const { mount: mountUpdatePipelineLogic } = new LogicMounter(UpdatePipelineApiLogic); + const { clearFlashMessages, flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; + + const newPipeline = { + ...DEFAULT_PIPELINE_VALUES, + name: 'new_pipeline_name', + run_ml_inference: true, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mountFetchIndexApiLogic(); + mountUpdatePipelineLogic(); + mount(); + }); + + it('has expected default values', () => { + expect(PipelinesLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('actions', () => { + it('should set showModal to false and call fetchApiSuccess', async () => { + FetchIndexApiLogic.actions.apiSuccess(connectorIndex); + PipelinesLogic.actions.fetchIndexApiSuccess = jest.fn(); + PipelinesLogic.actions.setPipelineState(newPipeline); + PipelinesLogic.actions.openModal(); + PipelinesLogic.actions.apiSuccess({ connectorId: 'a', pipeline: newPipeline }); + expect(PipelinesLogic.values).toEqual({ + ...DEFAULT_VALUES, + index: { + ...connectorIndex, + connector: { ...connectorIndex.connector }, + }, + }); + expect(flashSuccessToast).toHaveBeenCalled(); + expect(PipelinesLogic.actions.fetchIndexApiSuccess).toHaveBeenCalledWith({ + ...connectorIndex, + connector: { + ...connectorIndex.connector, + pipeline: newPipeline, + }, + }); + }); + it('should set pipelineState on setPipeline', () => { + PipelinesLogic.actions.setPipelineState({ + ...DEFAULT_PIPELINE_VALUES, + name: 'new_pipeline_name', + }); + expect(PipelinesLogic.values).toEqual({ + ...DEFAULT_VALUES, + pipelineState: { ...DEFAULT_PIPELINE_VALUES, name: 'new_pipeline_name' }, + }); + }); + describe('makeRequest', () => { + it('should call clearFlashMessages', () => { + PipelinesLogic.actions.makeRequest({ connectorId: 'a', pipeline: DEFAULT_PIPELINE_VALUES }); + expect(clearFlashMessages).toHaveBeenCalled(); + }); + }); + describe('openModal', () => { + it('should set showModal to true', () => { + PipelinesLogic.actions.openModal(); + expect(PipelinesLogic.values).toEqual({ ...DEFAULT_VALUES, showModal: true }); + }); + }); + describe('closeModal', () => { + it('should set showModal to false', () => { + PipelinesLogic.actions.openModal(); + PipelinesLogic.actions.closeModal(); + expect(PipelinesLogic.values).toEqual({ ...DEFAULT_VALUES, showModal: false }); + }); + }); + describe('apiError', () => { + it('should call flashAPIError', () => { + PipelinesLogic.actions.apiError('error' as any); + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); + }); + describe('apiSuccess', () => { + it('should call flashSuccessToast', () => { + PipelinesLogic.actions.apiSuccess({ connectorId: 'a', pipeline: newPipeline }); + expect(flashSuccessToast).toHaveBeenCalledWith('Pipelines successfully updated'); + }); + }); + describe('createCustomPipelineError', () => { + it('should call flashAPIError', () => { + PipelinesLogic.actions.createCustomPipelineError('error' as any); + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); + }); + describe('createCustomPipelineSuccess', () => { + it('should call flashSuccessToast', () => { + PipelinesLogic.actions.setPipelineState = jest.fn(); + PipelinesLogic.actions.savePipeline = jest.fn(); + PipelinesLogic.actions.fetchCustomPipeline = jest.fn(); + PipelinesLogic.actions.fetchIndexApiSuccess(connectorIndex); + PipelinesLogic.actions.createCustomPipelineSuccess({ created: ['a', 'b'] }); + expect(flashSuccessToast).toHaveBeenCalledWith('Custom pipeline successfully created'); + expect(PipelinesLogic.actions.setPipelineState).toHaveBeenCalledWith({ + ...PipelinesLogic.values.pipelineState, + name: 'a', + }); + expect(PipelinesLogic.actions.savePipeline).toHaveBeenCalled(); + expect(PipelinesLogic.actions.fetchCustomPipeline).toHaveBeenCalled(); + }); + }); + describe('fetchIndexApiSuccess', () => { + it('should set pipelineState if not editing', () => { + PipelinesLogic.actions.fetchIndexApiSuccess({ + ...connectorIndex, + connector: { ...connectorIndex.connector, pipeline: newPipeline }, + }); + expect(PipelinesLogic.values).toEqual({ + ...DEFAULT_VALUES, + canUseMlInferencePipeline: true, + index: { + ...connectorIndex, + connector: { ...connectorIndex.connector, pipeline: newPipeline }, + }, + pipelineState: newPipeline, + }); + }); + it('should not set configState if modal is open', () => { + PipelinesLogic.actions.openModal(); + PipelinesLogic.actions.fetchIndexApiSuccess({ + ...connectorIndex, + connector: { ...connectorIndex.connector, pipeline: newPipeline }, + }); + expect(PipelinesLogic.values).toEqual({ + ...DEFAULT_VALUES, + index: { + ...connectorIndex, + connector: { ...connectorIndex.connector, pipeline: newPipeline }, + }, + showModal: true, + }); + }); + }); + describe('savePipeline', () => { + it('should call makeRequest', () => { + PipelinesLogic.actions.makeRequest = jest.fn(); + PipelinesLogic.actions.fetchIndexApiSuccess(connectorIndex); + PipelinesLogic.actions.savePipeline(); + expect(PipelinesLogic.actions.makeRequest).toHaveBeenCalledWith({ + connectorId: '2', + pipeline: DEFAULT_PIPELINE_VALUES, + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts new file mode 100644 index 0000000000000..6b348f3ec94f8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts @@ -0,0 +1,243 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { i18n } from '@kbn/i18n'; + +import { DEFAULT_PIPELINE_VALUES } from '../../../../../../common/constants'; + +import { HttpError } from '../../../../../../common/types/api'; +import { IngestPipelineParams } from '../../../../../../common/types/connectors'; +import { ElasticsearchIndexWithIngestion } from '../../../../../../common/types/indices'; +import { InferencePipeline } from '../../../../../../common/types/pipelines'; +import { Actions } from '../../../../shared/api_logic/create_api_logic'; +import { + clearFlashMessages, + flashAPIErrors, + flashSuccessToast, +} from '../../../../shared/flash_messages'; + +import { + FetchDefaultPipelineApiLogic, + FetchDefaultPipelineResponse, +} from '../../../api/connector/get_default_pipeline_api_logic'; +import { + PostPipelineArgs, + PostPipelineResponse, + UpdatePipelineApiLogic, +} from '../../../api/connector/update_pipeline_api_logic'; +import { + CreateCustomPipelineApiLogic, + CreateCustomPipelineApiLogicArgs, + CreateCustomPipelineApiLogicResponse, +} from '../../../api/index/create_custom_pipeline_api_logic'; +import { + FetchCustomPipelineApiLogicArgs, + FetchCustomPipelineApiLogicResponse, + FetchCustomPipelineApiLogic, +} from '../../../api/index/fetch_custom_pipeline_api_logic'; +import { + FetchIndexApiLogic, + FetchIndexApiParams, + FetchIndexApiResponse, +} from '../../../api/index/fetch_index_api_logic'; +import { FetchMlInferencePipelineProcessorsApiLogic } from '../../../api/pipelines/fetch_ml_inference_pipeline_processors'; +import { isApiIndex, isConnectorIndex, isCrawlerIndex } from '../../../utils/indices'; + +type PipelinesActions = Pick< + Actions, + 'apiError' | 'apiSuccess' | 'makeRequest' +> & { + closeModal: () => void; + createCustomPipeline: Actions< + CreateCustomPipelineApiLogicArgs, + CreateCustomPipelineApiLogicResponse + >['makeRequest']; + createCustomPipelineError: Actions< + CreateCustomPipelineApiLogicArgs, + CreateCustomPipelineApiLogicResponse + >['apiError']; + createCustomPipelineSuccess: Actions< + CreateCustomPipelineApiLogicArgs, + CreateCustomPipelineApiLogicResponse + >['apiSuccess']; + fetchCustomPipeline: Actions< + FetchCustomPipelineApiLogicArgs, + FetchCustomPipelineApiLogicResponse + >['makeRequest']; + fetchDefaultPipeline: Actions['makeRequest']; + fetchDefaultPipelineSuccess: Actions['apiSuccess']; + fetchIndexApiSuccess: Actions['apiSuccess']; + fetchMlInferenceProcessors: typeof FetchMlInferencePipelineProcessorsApiLogic.actions.makeRequest; + fetchMlInferenceProcessorsApiError: (error: HttpError) => HttpError; + openModal: () => void; + savePipeline: () => void; + setPipelineState(pipeline: IngestPipelineParams): { + pipeline: IngestPipelineParams; + }; +}; + +interface PipelinesValues { + canSetPipeline: boolean; + canUseMlInferencePipeline: boolean; + defaultPipelineValues: IngestPipelineParams; + defaultPipelineValuesData: IngestPipelineParams | null; + index: FetchIndexApiResponse; + mlInferencePipelineProcessors: InferencePipeline[]; + pipelineState: IngestPipelineParams; + showModal: boolean; +} + +export const PipelinesLogic = kea>({ + actions: { + closeModal: true, + openModal: true, + savePipeline: true, + setPipelineState: (pipeline: IngestPipelineParams) => ({ pipeline }), + }, + connect: { + actions: [ + CreateCustomPipelineApiLogic, + [ + 'apiError as createCustomPipelineError', + 'apiSuccess as createCustomPipelineSuccess', + 'makeRequest as createCustomPipeline', + ], + UpdatePipelineApiLogic, + ['apiSuccess', 'apiError', 'makeRequest'], + FetchIndexApiLogic, + ['apiSuccess as fetchIndexApiSuccess'], + FetchDefaultPipelineApiLogic, + ['apiSuccess as fetchDefaultPipelineSuccess', 'makeRequest as fetchDefaultPipeline'], + FetchCustomPipelineApiLogic, + ['makeRequest as fetchCustomPipeline'], + FetchMlInferencePipelineProcessorsApiLogic, + [ + 'makeRequest as fetchMlInferenceProcessors', + 'apiError as fetchMlInferenceProcessorsApiError', + ], + ], + values: [ + FetchDefaultPipelineApiLogic, + ['data as defaultPipelineValuesData'], + FetchIndexApiLogic, + ['data as index'], + FetchMlInferencePipelineProcessorsApiLogic, + ['data as mlInferencePipelineProcessors'], + ], + }, + events: ({ actions, values }) => ({ + afterMount: () => { + actions.fetchDefaultPipeline(undefined); + actions.setPipelineState( + isConnectorIndex(values.index) || isCrawlerIndex(values.index) + ? values.index.connector?.pipeline ?? values.defaultPipelineValues + : values.defaultPipelineValues + ); + }, + }), + listeners: ({ actions, values }) => ({ + apiError: (error) => flashAPIErrors(error), + apiSuccess: ({ pipeline }) => { + if (isConnectorIndex(values.index) || isCrawlerIndex(values.index)) { + if (values.index.connector) { + // had to split up these if checks rather than nest them or typescript wouldn't recognize connector as defined + actions.fetchIndexApiSuccess({ + ...values.index, + connector: { ...values.index.connector, pipeline }, + }); + } + } + flashSuccessToast( + i18n.translate('xpack.enterpriseSearch.content.indices.pipelines.successToast.title', { + defaultMessage: 'Pipelines successfully updated', + }) + ); + }, + closeModal: () => + actions.setPipelineState( + isConnectorIndex(values.index) || isCrawlerIndex(values.index) + ? values.index.connector?.pipeline ?? values.defaultPipelineValues + : values.defaultPipelineValues + ), + createCustomPipelineError: (error) => flashAPIErrors(error), + createCustomPipelineSuccess: ({ created }) => { + flashSuccessToast( + i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.successToastCustom.title', + { + defaultMessage: 'Custom pipeline successfully created', + } + ) + ); + actions.setPipelineState({ ...values.pipelineState, name: created[0] }); + actions.savePipeline(); + actions.fetchCustomPipeline({ indexName: values.index.name }); + }, + fetchIndexApiSuccess: (index) => { + if (!values.showModal) { + // Don't do this when the modal is open to avoid overwriting the values while editing + const pipeline = + isConnectorIndex(index) || isCrawlerIndex(index) + ? index.connector?.pipeline + : values.defaultPipelineValues; + actions.setPipelineState(pipeline ?? values.defaultPipelineValues); + } + }, + makeRequest: () => clearFlashMessages(), + openModal: () => { + const pipeline = + isCrawlerIndex(values.index) || isConnectorIndex(values.index) + ? values.index.connector?.pipeline + : values.defaultPipelineValues; + actions.setPipelineState(pipeline ?? values.defaultPipelineValues); + }, + savePipeline: () => { + if (isConnectorIndex(values.index) || isCrawlerIndex(values.index)) { + if (values.index.connector) { + actions.makeRequest({ + connectorId: values.index.connector?.id, + pipeline: values.pipelineState, + }); + } + } + }, + }), + path: ['enterprise_search', 'content', 'pipelines'], + reducers: () => ({ + pipelineState: [ + DEFAULT_PIPELINE_VALUES, + { + setPipelineState: (_, { pipeline }) => pipeline, + }, + ], + showModal: [ + false, + { + apiSuccess: () => false, + closeModal: () => false, + openModal: () => true, + }, + ], + }), + selectors: ({ selectors }) => ({ + canSetPipeline: [ + () => [selectors.index], + (index: ElasticsearchIndexWithIngestion) => !isApiIndex(index), + ], + canUseMlInferencePipeline: [ + () => [selectors.canSetPipeline, selectors.pipelineState], + (canSetPipeline: boolean, pipelineState: IngestPipelineParams) => + canSetPipeline && pipelineState.run_ml_inference, + ], + defaultPipelineValues: [ + () => [selectors.defaultPipelineValuesData], + (pipeline: IngestPipelineParams | null) => pipeline ?? DEFAULT_PIPELINE_VALUES, + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx index 58494595ad2e4..47cdc0d05753b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { useParams } from 'react-router-dom'; @@ -14,14 +14,16 @@ import { useValues } from 'kea'; import { EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; import { Status } from '../../../../../common/types/api'; -import { enableIndexPipelinesTab } from '../../../../../common/ui_settings_keys'; import { generateEncodedPath } from '../../../shared/encode_path_params'; import { KibanaLogic } from '../../../shared/kibana'; import { FetchIndexApiLogic } from '../../api/index/fetch_index_api_logic'; -import { SEARCH_INDEX_PATH, SEARCH_INDEX_TAB_PATH } from '../../routes'; +import { + SEARCH_INDEX_PATH, + SEARCH_INDEX_SELECT_CONNECTOR_PATH, + SEARCH_INDEX_TAB_PATH, +} from '../../routes'; import { isConnectorIndex, isCrawlerIndex } from '../../utils/indices'; import { EnterpriseSearchContentPageTemplate } from '../layout/page_template'; @@ -60,13 +62,20 @@ export const SearchIndex: React.FC = () => { const { tabId = SearchIndexTabId.OVERVIEW } = useParams<{ tabId?: string; }>(); - const { - services: { uiSettings }, - } = useKibana(); const { indexName } = useValues(IndexNameLogic); - const pipelinesEnabled = uiSettings?.get(enableIndexPipelinesTab) ?? false; + useEffect(() => { + if ( + isConnectorIndex(indexData) && + indexData.connector.is_native && + indexData.connector.service_type === null + ) { + KibanaLogic.values.navigateToUrl( + generateEncodedPath(SEARCH_INDEX_SELECT_CONNECTOR_PATH, { indexName }) + ); + } + }, [indexData]); const ALL_INDICES_TABS: EuiTabbedContentTab[] = [ { @@ -126,21 +135,19 @@ export const SearchIndex: React.FC = () => { }, ]; - const PIPELINES_TAB: EuiTabbedContentTab[] = [ - { - content: , - id: SearchIndexTabId.PIPELINES, - name: i18n.translate('xpack.enterpriseSearch.content.searchIndex.pipelinesTabLabel', { - defaultMessage: 'Pipelines', - }), - }, - ]; + const PIPELINES_TAB: EuiTabbedContentTab = { + content: , + id: SearchIndexTabId.PIPELINES, + name: i18n.translate('xpack.enterpriseSearch.content.searchIndex.pipelinesTabLabel', { + defaultMessage: 'Pipelines', + }), + }; const tabs: EuiTabbedContentTab[] = [ ...ALL_INDICES_TABS, ...(isConnectorIndex(indexData) ? CONNECTOR_TABS : []), ...(isCrawlerIndex(indexData) ? CRAWLER_TABS : []), - ...(pipelinesEnabled ? PIPELINES_TAB : []), + PIPELINES_TAB, ]; const selectedTab = tabs.find((tab) => tab.id === tabId); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index_router.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index_router.tsx index 58a4cd782c5e2..f278f6b3d7fa4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index_router.tsx @@ -13,11 +13,13 @@ import { useActions } from 'kea'; import { SEARCH_INDEX_CRAWLER_DOMAIN_DETAIL_PATH, SEARCH_INDEX_PATH, + SEARCH_INDEX_SELECT_CONNECTOR_PATH, SEARCH_INDEX_TAB_PATH, } from '../../routes'; import { CrawlerDomainDetail } from '../crawler_domain_detail/crawler_domain_detail'; +import { SelectConnector } from './connector/select_connector/select_connector'; import { IndexNameLogic } from './index_name_logic'; import { IndexViewLogic } from './index_view_logic'; import { SearchIndex } from './search_index'; @@ -51,6 +53,9 @@ export const SearchIndexRouter: React.FC = () => { + + + diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx index 4255d467d4676..438ed424f3c07 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx @@ -7,11 +7,26 @@ import React from 'react'; +import { useActions, useValues } from 'kea'; + +import { EuiButton, EuiLink, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { EnterpriseSearchContentPageTemplate } from '../layout/page_template'; +import { SettingsLogic } from './settings_logic'; +import { SettingsPanel } from './settings_panel'; + export const Settings: React.FC = () => { + const { makeRequest, setPipeline } = useActions(SettingsLogic); + const { defaultPipeline, hasNoChanges, isLoading, pipelineState } = useValues(SettingsLogic); + + const { + extract_binary_content: extractBinaryContent, + reduce_whitespace: reduceWhitespace, + run_ml_inference: runMLInference, + } = pipelineState; + return ( { defaultMessage: 'Settings', }), ]} + pageHeader={{ + pageTitle: i18n.translate('xpack.enterpriseSearch.content.settings.headerTitle', { + defaultMessage: 'Content Settings', + }), + rightSideItems: [ + makeRequest(pipelineState)} + > + {i18n.translate('xpack.enterpriseSearch.content.settings.saveButtonLabel', { + defaultMessage: 'Save', + })} + , + setPipeline(defaultPipeline)} + > + {i18n.translate('xpack.enterpriseSearch.content.settings.resetButtonLabel', { + defaultMessage: 'Reset', + })} + , + ], + }} pageViewTelemetry="Settings" isLoading={false} > - <>Settings + + {i18n.translate('xpack.enterpriseSearch.content.settings.contactExtraction.link', { + defaultMessage: 'Learn more about content extraction', + })} + + } + onChange={() => + setPipeline({ + ...pipelineState, + extract_binary_content: !pipelineState.extract_binary_content, + }) + } + title={i18n.translate('xpack.enterpriseSearch.content.settings.contentExtraction.title', { + defaultMessage: 'Deployment wide content extraction', + })} + value={extractBinaryContent} + /> + + + {i18n.translate('xpack.enterpriseSearch.content.settings.whitespaceReduction.link', { + defaultMessage: 'Learn more about whitespace reduction', + })} + + } + onChange={() => + setPipeline({ + ...pipelineState, + reduce_whitespace: !pipelineState.reduce_whitespace, + }) + } + title={i18n.translate( + 'xpack.enterpriseSearch.content.settings.whitespaceReduction.deploymentHeaderTitle', + { + defaultMessage: 'Deployment wide whitespace reduction', + } + )} + value={reduceWhitespace} + /> + + + {i18n.translate('xpack.enterpriseSearch.content.settings.mlInference.link', { + defaultMessage: 'Learn more about content extraction', + })} + + } + onChange={() => + setPipeline({ + ...pipelineState, + run_ml_inference: !pipelineState.run_ml_inference, + }) + } + title={i18n.translate( + 'xpack.enterpriseSearch.content.settings.mlInference.deploymentHeaderTitle', + { + defaultMessage: 'Deployment wide ML Inference Pipelines extraction', + } + )} + value={runMLInference} + /> ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings_logic.ts new file mode 100644 index 0000000000000..11c63043e989a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings_logic.ts @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { isDeepEqual } from 'react-use/lib/util'; + +import { i18n } from '@kbn/i18n'; + +import { DEFAULT_PIPELINE_VALUES } from '../../../../../common/constants'; +import { Status } from '../../../../../common/types/api'; + +import { IngestPipelineParams } from '../../../../../common/types/connectors'; +import { Actions } from '../../../shared/api_logic/create_api_logic'; +import { + clearFlashMessages, + flashAPIErrors, + flashSuccessToast, +} from '../../../shared/flash_messages'; + +import { + FetchDefaultPipelineApiLogic, + FetchDefaultPipelineResponse, +} from '../../api/connector/get_default_pipeline_api_logic'; +import { + PostDefaultPipelineArgs, + PostDefaultPipelineResponse, + UpdateDefaultPipelineApiLogic, +} from '../../api/connector/update_default_pipeline_api_logic'; + +type PipelinesActions = Pick< + Actions, + 'apiError' | 'apiSuccess' | 'makeRequest' +> & { + fetchDefaultPipeline: Actions['makeRequest']; + fetchDefaultPipelineError: Actions['apiError']; + fetchDefaultPipelineSuccess: Actions['apiSuccess']; + setPipeline(pipeline: IngestPipelineParams): { + pipeline: IngestPipelineParams; + }; +}; + +interface PipelinesValues { + defaultPipeline: IngestPipelineParams; + fetchStatus: Status; + hasNoChanges: boolean; + isLoading: boolean; + pipelineState: IngestPipelineParams; + status: Status; +} + +export const SettingsLogic = kea>({ + actions: { + setPipeline: (pipeline: IngestPipelineParams) => ({ pipeline }), + }, + connect: { + actions: [ + UpdateDefaultPipelineApiLogic, + ['apiSuccess', 'apiError', 'makeRequest'], + FetchDefaultPipelineApiLogic, + [ + 'apiError as fetchDefaultPipelineError', + 'apiSuccess as fetchDefaultPipelineSuccess', + 'makeRequest as fetchDefaultPipeline', + ], + ], + values: [ + FetchDefaultPipelineApiLogic, + ['data as defaultPipeline', 'status as fetchStatus'], + UpdateDefaultPipelineApiLogic, + ['status'], + ], + }, + events: ({ actions }) => ({ + afterMount: () => { + actions.fetchDefaultPipeline(undefined); + }, + }), + listeners: ({ actions }) => ({ + apiError: (error) => flashAPIErrors(error), + apiSuccess: (pipeline) => { + flashSuccessToast( + i18n.translate( + 'xpack.enterpriseSearch.content.indices.defaultPipelines.successToast.title', + { + defaultMessage: 'Default pipeline successfully updated', + } + ) + ); + actions.fetchDefaultPipelineSuccess(pipeline); + }, + fetchDefaultPipelineSuccess: (pipeline) => { + actions.setPipeline(pipeline); + }, + makeRequest: () => clearFlashMessages(), + }), + path: ['enterprise_search', 'content', 'settings'], + reducers: () => ({ + pipelineState: [ + DEFAULT_PIPELINE_VALUES, + { + setPipeline: (_, { pipeline }) => pipeline, + }, + ], + showModal: [ + false, + { + apiSuccess: () => false, + closeModal: () => false, + openModal: () => true, + }, + ], + }), + selectors: ({ selectors }) => ({ + hasNoChanges: [ + () => [selectors.pipelineState, selectors.defaultPipeline], + (pipelineState: IngestPipelineParams, defaultPipeline: IngestPipelineParams) => + isDeepEqual(pipelineState, defaultPipeline), + ], + isLoading: [ + () => [selectors.status, selectors.fetchStatus], + (status, fetchStatus) => + [Status.LOADING, Status.IDLE].includes(fetchStatus) || status === Status.LOADING, + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings_panel.tsx new file mode 100644 index 0000000000000..154da2649f9ae --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings_panel.tsx @@ -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 React from 'react'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiSplitPanel, + EuiSwitch, + EuiSwitchEvent, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface SettingsPanelProps { + description: string; + link: React.ReactNode; + onChange: (event: EuiSwitchEvent) => void; + title: string; + value: boolean; +} + +export const SettingsPanel: React.FC = ({ + description, + link, + onChange, + title, + value, +}) => ( + + + +

+ {title} +

+
+ + +

{description}

+

+ {i18n.translate( + 'xpack.enterpriseSearch.content.settings.contentExtraction.descriptionTwo', + { + defaultMessage: + 'You can also enable or disable this feature for a specific index on the index’s configuration page.', + } + )} +

+
+
+ + + + + + {link} + + +
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/settings_checkable_card/settings_checkable_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/settings_checkable_card/settings_checkable_card.tsx new file mode 100644 index 0000000000000..00f91b5aff701 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/settings_checkable_card/settings_checkable_card.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 from 'react'; + +import { EuiCheckableCard, EuiText, EuiTitle } from '@elastic/eui'; + +export const SettingsCheckableCard: React.FC<{ + checked: boolean; + description: string; + id: string; + label: string; + onChange: React.ChangeEventHandler; +}> = ({ checked, description, id, label, onChange }) => ( + +

{label}

+ + } + checkableType="checkbox" + onChange={onChange} + checked={checked} + id={id} + > + +

{description}

+
+
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts index 045920c8043fe..60260dcaa5377 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts @@ -20,3 +20,4 @@ export const NEW_DIRECT_UPLOAD_PATH = `${NEW_INDEX_PATH}/upload`; export const SEARCH_INDEX_PATH = `${SEARCH_INDICES_PATH}/:indexName`; export const SEARCH_INDEX_TAB_PATH = `${SEARCH_INDEX_PATH}/:tabId`; export const SEARCH_INDEX_CRAWLER_DOMAIN_DETAIL_PATH = `${SEARCH_INDEX_PATH}/crawler/domains/:domainId`; +export const SEARCH_INDEX_SELECT_CONNECTOR_PATH = `${SEARCH_INDEX_PATH}/select_connector`; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/has_configured_configuration.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/has_configured_configuration.ts new file mode 100644 index 0000000000000..ad8188e177985 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/has_configured_configuration.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ConnectorConfiguration } from '../../../../common/types/connectors'; + +export const hasConfiguredConfiguration = (configuration: ConnectorConfiguration) => { + // debugger; + return !!Object.entries(configuration).find(([, pair]) => !!pair?.value); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index 5af50fce587fe..2e34733f919a7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -54,10 +54,12 @@ export const renderApp = ( const store = getContext().store; const unmountKibanaLogic = mountKibanaLogic({ + capabilities: core.application.capabilities, config, productAccess, charts: plugins.charts, cloud: plugins.cloud, + uiSettings: core.uiSettings, history: params.history, navigateToUrl: core.application.navigateToUrl, security: plugins.security, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts b/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts index 8e6159d2b5b2a..88cec898afcb0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts @@ -10,6 +10,19 @@ import { i18n } from '@kbn/i18n'; export const USERNAME_LABEL = i18n.translate('xpack.enterpriseSearch.usernameLabel', { defaultMessage: 'Username', }); + +export const PASSWORD_LABEL = i18n.translate('xpack.enterpriseSearch.passwordLabel', { + defaultMessage: 'Password', +}); + +export const TOKEN_LABEL = i18n.translate('xpack.enterpriseSearch.tokenLabel', { + defaultMessage: 'Token', +}); + +export const TYPE_LABEL = i18n.translate('xpack.enterpriseSearch.typeLabel', { + defaultMessage: 'Type', +}); + export const EMAIL_LABEL = i18n.translate('xpack.enterpriseSearch.emailLabel', { defaultMessage: 'Email', }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/data_panel/data_panel.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/data_panel/data_panel.test.tsx index 547dc87e27486..917e3ab649476 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/data_panel/data_panel.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/data_panel/data_panel.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { EuiIcon, EuiButton, EuiTitle, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; +import { EuiIcon, EuiButton, EuiTitle, EuiSpacer } from '@elastic/eui'; import { LoadingOverlay } from '../loading'; @@ -94,16 +94,6 @@ describe('DataPanel', () => { expect(wrapper.find(EuiTitle).prop('size')).toEqual('s'); }); - it('passes responsive to the header flex group', () => { - const wrapper = shallow(Test

} />); - - expect(wrapper.find(EuiFlexGroup).first().prop('responsive')).toEqual(false); - - wrapper.setProps({ responsive: true }); - - expect(wrapper.find(EuiFlexGroup).first().prop('responsive')).toEqual(true); - }); - it('renders panel color based on filled flag', () => { const wrapper = shallow(Test} />); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/data_panel/data_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/data_panel/data_panel.tsx index 0d4820a531c35..dd3d6b28f2547 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/data_panel/data_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/data_panel/data_panel.tsx @@ -32,7 +32,6 @@ type Props = Omit<_EuiPanelDivlike, 'title'> & { iconType?: EuiIconProps['type']; action?: React.ReactNode; footerDocLink?: React.ReactNode; - responsive?: boolean; filled?: boolean; isLoading?: boolean; className?: string; @@ -44,7 +43,6 @@ export const DataPanel: React.FC = ({ subtitle, iconType, action, - responsive = false, filled, isLoading, footerDocLink, @@ -66,35 +64,29 @@ export const DataPanel: React.FC = ({ {...props} > - - + + - - - {iconType && ( - - - - )} - - {title} - - + {iconType && ( + + + + )} + + {title} - {action && {action}} - - {subtitle && ( - <> - - -

{subtitle}

-
- - )} -
+ {action && {action}}
+ {subtitle && ( + <> + + +

{subtitle}

+
+ + )} {children && ( <> diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts index 37ab1e972bbd5..21be13375b980 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts @@ -11,7 +11,13 @@ import { kea, MakeLogicType } from 'kea'; import { ChartsPluginStart } from '@kbn/charts-plugin/public'; import { CloudSetup } from '@kbn/cloud-plugin/public'; -import { ApplicationStart, ChromeBreadcrumb, ScopedHistory } from '@kbn/core/public'; +import { + ApplicationStart, + Capabilities, + ChromeBreadcrumb, + ScopedHistory, + IUiSettingsClient, +} from '@kbn/core/public'; import { SecurityPluginStart } from '@kbn/security-plugin/public'; import { ProductAccess } from '../../../../common/types'; @@ -26,6 +32,7 @@ interface KibanaLogicProps { config: { host?: string }; productAccess: ProductAccess; // Kibana core + capabilities: Capabilities; history: ScopedHistory; navigateToUrl: RequiredFieldsOnly; setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void; @@ -35,6 +42,7 @@ interface KibanaLogicProps { // Required plugins charts: ChartsPluginStart; security: SecurityPluginStart; + uiSettings: IUiSettingsClient; // Optional plugins cloud?: CloudSetup; } @@ -47,6 +55,7 @@ export interface KibanaValues extends Omit { export const KibanaLogic = kea>({ path: ['enterprise_search', 'kibana_logic'], reducers: ({ props }) => ({ + capabilities: [props.capabilities || {}, {}], config: [props.config || {}, {}], charts: [props.charts, {}], cloud: [props.cloud || {}, {}], @@ -65,6 +74,7 @@ export const KibanaLogic = kea>({ setBreadcrumbs: [props.setBreadcrumbs, {}], setChromeIsVisible: [props.setChromeIsVisible, {}], setDocTitle: [props.setDocTitle, {}], + uiSettings: [props.uiSettings, {}], }), selectors: ({ selectors }) => ({ isCloud: [() => [selectors.cloud], (cloud?: Partial) => !!cloud?.isCloudEnabled], 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 523f435c8a2fd..743efc3274a44 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 @@ -9,10 +9,12 @@ jest.mock('./nav_link_helpers', () => ({ generateNavLink: jest.fn(({ to, items }) => ({ href: to, items })), })); -import { setMockValues } from '../../__mocks__/kea_logic'; +import { setMockValues, mockKibanaValues } from '../../__mocks__/kea_logic'; import { ProductAccess } from '../../../../common/types'; +import { enableBehavioralAnalyticsSection } from '../../../../common/ui_settings_keys'; + import { useEnterpriseSearchNav } from './nav'; describe('useEnterpriseSearchContentNav', () => { @@ -26,6 +28,7 @@ describe('useEnterpriseSearchContentNav', () => { hasWorkplaceSearchAccess: true, }; setMockValues({ productAccess: fullProductAccess }); + mockKibanaValues.uiSettings.get.mockReturnValue(true); expect(useEnterpriseSearchNav()).toEqual([ { @@ -41,12 +44,17 @@ describe('useEnterpriseSearchContentNav', () => { id: 'search_indices', name: 'Indices', }, + { + href: '/app/enterprise_search/content/settings', + id: 'settings', + items: undefined, + name: 'Settings', + }, ], name: 'Content', }, { id: 'enterpriseSearchAnalytics', - name: 'Analytics', items: [ { href: '/app/enterprise_search/analytics', @@ -54,6 +62,7 @@ describe('useEnterpriseSearchContentNav', () => { name: 'Collections', }, ], + name: 'Analytics', }, { id: 'search', @@ -77,6 +86,7 @@ describe('useEnterpriseSearchContentNav', () => { name: 'Search', }, ]); + expect(mockKibanaValues.uiSettings.get).toHaveBeenCalledWith(enableBehavioralAnalyticsSection); }); it('excludes legacy products when the user has no access to them', () => { 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 c5e126d9c88e1..f5a2d17d7dd94 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 @@ -18,13 +18,17 @@ import { ENTERPRISE_SEARCH_OVERVIEW_PLUGIN, WORKPLACE_SEARCH_PLUGIN, } from '../../../../common/constants'; -import { SEARCH_INDICES_PATH } from '../../enterprise_search_content/routes'; +import { enableBehavioralAnalyticsSection } from '../../../../common/ui_settings_keys'; +import { SEARCH_INDICES_PATH, SETTINGS_PATH } from '../../enterprise_search_content/routes'; import { KibanaLogic } from '../kibana'; import { generateNavLink } from './nav_link_helpers'; export const useEnterpriseSearchNav = () => { - const { productAccess } = useValues(KibanaLogic); + const { productAccess, uiSettings } = useValues(KibanaLogic); + + const analyticsSectionEnabled = + uiSettings?.get(enableBehavioralAnalyticsSection) ?? false; const navItems: Array> = [ { @@ -51,30 +55,45 @@ export const useEnterpriseSearchNav = () => { to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + SEARCH_INDICES_PATH, }), }, - ], - name: i18n.translate('xpack.enterpriseSearch.nav.contentTitle', { - defaultMessage: 'Content', - }), - }, - { - id: 'enterpriseSearchAnalytics', - items: [ { - id: 'analytics_collections', - name: i18n.translate('xpack.enterpriseSearch.nav.analyticsCollectionsTitle', { - defaultMessage: 'Collections', + id: 'settings', + name: i18n.translate('xpack.enterpriseSearch.nav.contentSettingsTitle', { + defaultMessage: 'Settings', }), ...generateNavLink({ shouldNotCreateHref: true, shouldShowActiveForSubroutes: true, - to: ANALYTICS_PLUGIN.URL, + to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + SETTINGS_PATH, }), }, ], - name: i18n.translate('xpack.enterpriseSearch.nav.analyticsTitle', { - defaultMessage: 'Analytics', + name: i18n.translate('xpack.enterpriseSearch.nav.contentTitle', { + defaultMessage: 'Content', }), }, + ...(analyticsSectionEnabled + ? [ + { + id: 'enterpriseSearchAnalytics', + items: [ + { + id: 'analytics_collections', + name: i18n.translate('xpack.enterpriseSearch.nav.analyticsCollectionsTitle', { + defaultMessage: 'Collections', + }), + ...generateNavLink({ + shouldNotCreateHref: true, + shouldShowActiveForSubroutes: true, + to: ANALYTICS_PLUGIN.URL, + }), + }, + ], + name: i18n.translate('xpack.enterpriseSearch.nav.analyticsTitle', { + defaultMessage: 'Analytics', + }), + }, + ] + : []), { id: 'search', items: [ diff --git a/x-pack/plugins/enterprise_search/public/assets/images/no_ml_models_dark.svg b/x-pack/plugins/enterprise_search/public/assets/images/no_ml_models_dark.svg new file mode 100644 index 0000000000000..9e48c569fd940 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/assets/images/no_ml_models_dark.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/x-pack/plugins/enterprise_search/public/assets/images/no_ml_models_light.svg b/x-pack/plugins/enterprise_search/public/assets/images/no_ml_models_light.svg new file mode 100644 index 0000000000000..636a8033e6f79 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/assets/images/no_ml_models_light.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/x-pack/plugins/enterprise_search/public/assets/source_icons/mongodb.svg b/x-pack/plugins/enterprise_search/public/assets/source_icons/mongodb.svg new file mode 100644 index 0000000000000..5fefc08e34464 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/assets/source_icons/mongodb.svg @@ -0,0 +1,5 @@ + + + diff --git a/x-pack/plugins/enterprise_search/public/assets/source_icons/mysql.svg b/x-pack/plugins/enterprise_search/public/assets/source_icons/mysql.svg new file mode 100644 index 0000000000000..096a2e25efe1a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/assets/source_icons/mysql.svg @@ -0,0 +1,5 @@ + + + diff --git a/x-pack/plugins/enterprise_search/public/assets/source_icons/native_connector_icons.ts b/x-pack/plugins/enterprise_search/public/assets/source_icons/native_connector_icons.ts new file mode 100644 index 0000000000000..c6f210a3ec8d4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/assets/source_icons/native_connector_icons.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import mongodb from './mongodb.svg'; +import mysql from './mysql.svg'; + +export const NATIVE_CONNECTOR_ICONS: Record = { + mongodb, + mysql, +}; diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index 7d3478392040c..6dff94d411597 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -31,6 +31,8 @@ import { } from '../common/constants'; import { InitialAppData } from '../common/types'; +import { enableBehavioralAnalyticsSection } from '../common/ui_settings_keys'; + import { docLinks } from './applications/shared/doc_links'; export interface ClientConfigType { @@ -68,6 +70,10 @@ export class EnterpriseSearchPlugin implements Plugin { public setup(core: CoreSetup, plugins: PluginsSetup) { const { cloud } = plugins; + const bahavioralAnalyticsEnabled = core.uiSettings?.get( + enableBehavioralAnalyticsSection + ); + core.application.register({ id: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.ID, title: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.NAV_TITLE, @@ -118,6 +124,7 @@ export class EnterpriseSearchPlugin implements Plugin { id: ANALYTICS_PLUGIN.ID, title: ANALYTICS_PLUGIN.NAME, euiIconType: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.LOGO, + searchable: bahavioralAnalyticsEnabled, appRoute: ANALYTICS_PLUGIN.URL, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, mount: async (params: AppMountParameters) => { @@ -211,15 +218,17 @@ export class EnterpriseSearchPlugin implements Plugin { order: 100, }); - plugins.home.featureCatalogue.register({ - id: ANALYTICS_PLUGIN.ID, - title: ANALYTICS_PLUGIN.NAME, - icon: 'appAnalytics', - description: ANALYTICS_PLUGIN.DESCRIPTION, - path: ANALYTICS_PLUGIN.URL, - category: 'data', - showOnHomePage: false, - }); + if (bahavioralAnalyticsEnabled) { + plugins.home.featureCatalogue.register({ + id: ANALYTICS_PLUGIN.ID, + title: ANALYTICS_PLUGIN.NAME, + icon: 'appAnalytics', + description: ANALYTICS_PLUGIN.DESCRIPTION, + path: ANALYTICS_PLUGIN.URL, + category: 'data', + showOnHomePage: false, + }); + } plugins.home.featureCatalogue.register({ id: APP_SEARCH_PLUGIN.ID, diff --git a/x-pack/plugins/enterprise_search/server/index.ts b/x-pack/plugins/enterprise_search/server/index.ts index 3c409c09ff642..dfcfa8ba4627c 100644 --- a/x-pack/plugins/enterprise_search/server/index.ts +++ b/x-pack/plugins/enterprise_search/server/index.ts @@ -39,6 +39,7 @@ export const config: PluginConfigDescriptor = { schema: configSchema, }; export const CONNECTORS_INDEX = '.elastic-connectors'; +export const CURRENT_CONNECTORS_INDEX = '.elastic-connectors-v1'; export const CONNECTORS_JOBS_INDEX = '.elastic-connectors-sync-jobs'; export const CONNECTORS_VERSION = '1'; export const CRAWLERS_INDEX = '.ent-search-actastic-crawler2_configurations'; diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts index d52bb2de6751d..68b9adf0aee79 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts @@ -12,18 +12,30 @@ import { CONNECTORS_INDEX } from '../..'; import { ConnectorDocument } from '../../../common/types/connectors'; import { ErrorCode } from '../../../common/types/error_codes'; -export const startConnectorSync = async (client: IScopedClusterClient, connectorId: string) => { +export const startConnectorSync = async ( + client: IScopedClusterClient, + connectorId: string, + nextSyncConfig?: string +) => { const connectorResult = await client.asCurrentUser.get({ id: connectorId, index: CONNECTORS_INDEX, }); const connector = connectorResult._source; if (connector) { + if (nextSyncConfig) { + connector.configuration.nextSyncConfig = { label: 'nextSyncConfig', value: nextSyncConfig }; + } + const result = await client.asCurrentUser.index({ - document: { ...connector, sync_now: true }, + document: { + ...connector, + sync_now: true, + }, id: connectorId, index: CONNECTORS_INDEX, }); + await client.asCurrentUser.indices.refresh({ index: CONNECTORS_INDEX }); return result; } else { diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_configuration.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_configuration.ts index 77feda4b7ff5b..ed1be0c2b6fa8 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_configuration.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_configuration.ts @@ -31,11 +31,13 @@ export const updateConnectorConfiguration = async ( connector.status === ConnectorStatus.NEEDS_CONFIGURATION ? ConnectorStatus.CONFIGURED : connector.status; - return await client.asCurrentUser.index({ + const result = await client.asCurrentUser.index({ document: { ...connector, configuration, status }, id: connectorId, index: CONNECTORS_INDEX, }); + await client.asCurrentUser.indices.refresh({ index: CONNECTORS_INDEX }); + return result; } else { throw new Error( i18n.translate('xpack.enterpriseSearch.server.connectors.configuration.error', { diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_service_type.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_service_type.ts new file mode 100644 index 0000000000000..a88950c48e99f --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_service_type.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 { IScopedClusterClient } from '@kbn/core/server'; +import { i18n } from '@kbn/i18n'; + +import { CONNECTORS_INDEX } from '../..'; + +import { ConnectorDocument } from '../../../common/types/connectors'; + +export const updateConnectorServiceType = async ( + client: IScopedClusterClient, + connectorId: string, + serviceType: string +) => { + const connectorResult = await client.asCurrentUser.get({ + id: connectorId, + index: CONNECTORS_INDEX, + }); + const connector = connectorResult._source; + if (connector) { + const result = await client.asCurrentUser.index({ + document: { ...connector, service_type: serviceType }, + id: connectorId, + index: CONNECTORS_INDEX, + }); + await client.asCurrentUser.indices.refresh({ index: CONNECTORS_INDEX }); + return result; + } else { + throw new Error( + i18n.translate('xpack.enterpriseSearch.server.connectors.serviceType.error', { + defaultMessage: 'Could not find document', + }) + ); + } +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_status.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_status.ts new file mode 100644 index 0000000000000..caf8bbc7c7ce9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_status.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 { IScopedClusterClient } from '@kbn/core/server'; +import { i18n } from '@kbn/i18n'; + +import { CONNECTORS_INDEX } from '../..'; + +import { ConnectorDocument, ConnectorStatus } from '../../../common/types/connectors'; + +export const updateConnectorStatus = async ( + client: IScopedClusterClient, + connectorId: string, + status: ConnectorStatus +) => { + const connectorResult = await client.asCurrentUser.get({ + id: connectorId, + index: CONNECTORS_INDEX, + }); + const connector = connectorResult._source; + if (connector) { + const result = await client.asCurrentUser.index({ + document: { ...connector, status }, + id: connectorId, + index: CONNECTORS_INDEX, + }); + await client.asCurrentUser.indices.refresh({ index: CONNECTORS_INDEX }); + return result; + } else { + throw new Error( + i18n.translate('xpack.enterpriseSearch.server.connectors.serviceType.error', { + defaultMessage: 'Could not find document', + }) + ); + } +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/delete_ml_inference_pipeline.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/delete_ml_inference_pipeline.test.ts new file mode 100644 index 0000000000000..8782dcc772d75 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/indices/delete_ml_inference_pipeline.test.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { errors } from '@elastic/elasticsearch'; +import { ElasticsearchClient } from '@kbn/core/server'; + +import { deleteMlInferencePipeline } from './delete_ml_inference_pipeline'; + +describe('deleteMlInferencePipeline lib function', () => { + const mockClient = { + ingest: { + deletePipeline: jest.fn(), + getPipeline: jest.fn(), + putPipeline: jest.fn(), + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + const anyObject: any = {}; + const notFoundResponse = { meta: { statusCode: 404 } }; + const notFoundError = new errors.ResponseError({ + body: notFoundResponse, + statusCode: 404, + headers: {}, + meta: anyObject, + warnings: [], + }); + const mockGetPipeline = { + 'my-index@ml-inference': { + id: 'my-index@ml-inference', + processors: [ + { + pipeline: { + name: 'my-ml-pipeline', + }, + }, + ], + }, + }; + + it('should delete pipeline', async () => { + mockClient.ingest.getPipeline.mockImplementation(() => Promise.resolve(mockGetPipeline)); + mockClient.ingest.putPipeline.mockImplementation(() => Promise.resolve({ acknowledged: true })); + mockClient.ingest.deletePipeline.mockImplementation(() => + Promise.resolve({ acknowledged: true }) + ); + + const expectedResponse = { deleted: 'my-ml-pipeline', updated: 'my-index@ml-inference' }; + + const response = await deleteMlInferencePipeline( + 'my-index', + 'my-ml-pipeline', + mockClient as unknown as ElasticsearchClient + ); + + expect(response).toEqual(expectedResponse); + + expect(mockClient.ingest.putPipeline).toHaveBeenCalledWith({ + id: 'my-index@ml-inference', + processors: [], + }); + expect(mockClient.ingest.deletePipeline).toHaveBeenCalledWith({ + id: 'my-ml-pipeline', + }); + }); + + it('should succeed when parent pipeline is missing', async () => { + mockClient.ingest.getPipeline.mockImplementation(() => Promise.reject(notFoundError)); + mockClient.ingest.deletePipeline.mockImplementation(() => + Promise.resolve({ acknowledged: true }) + ); + + const expectedResponse = { + deleted: 'my-ml-pipeline', + }; + + const response = await deleteMlInferencePipeline( + 'my-index', + 'my-ml-pipeline', + mockClient as unknown as ElasticsearchClient + ); + + expect(response).toEqual(expectedResponse); + + expect(mockClient.ingest.putPipeline).toHaveBeenCalledTimes(0); + expect(mockClient.ingest.deletePipeline).toHaveBeenCalledWith({ + id: 'my-ml-pipeline', + }); + }); + + it('should fail when pipeline is missing', async () => { + mockClient.ingest.getPipeline.mockImplementation(() => Promise.resolve(mockGetPipeline)); + mockClient.ingest.deletePipeline.mockImplementation(() => Promise.reject(notFoundError)); + + await expect( + deleteMlInferencePipeline( + 'my-index', + 'my-ml-pipeline', + mockClient as unknown as ElasticsearchClient + ) + ).rejects.toThrow(Error); + + expect(mockClient.ingest.putPipeline).toHaveBeenCalledWith({ + id: 'my-index@ml-inference', + processors: [], + }); + expect(mockClient.ingest.deletePipeline).toHaveBeenCalledWith({ + id: 'my-ml-pipeline', + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/delete_ml_inference_pipeline.ts b/x-pack/plugins/enterprise_search/server/lib/indices/delete_ml_inference_pipeline.ts new file mode 100644 index 0000000000000..282487e62f023 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/indices/delete_ml_inference_pipeline.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 { IngestPutPipelineRequest } from '@elastic/elasticsearch/lib/api/types'; +import { ElasticsearchClient } from '@kbn/core/server'; + +/** + * Response for deleting sub-pipeline from @ml-inference pipeline. + * If sub-pipeline was deleted successfully, 'deleted' field contains its name. + * If parent pipeline was updated successfully, 'updated' field contains its name. + */ +export interface DeleteMlInferencePipelineResponse { + deleted?: string; + updated?: string; +} + +export const deleteMlInferencePipeline = async ( + indexName: string, + pipelineName: string, + client: ElasticsearchClient +) => { + const response: DeleteMlInferencePipelineResponse = {}; + const parentPipelineId = `${indexName}@ml-inference`; + + // find parent pipeline + try { + const pipelineResponse = await client.ingest.getPipeline({ + id: parentPipelineId, + }); + + const parentPipeline = pipelineResponse[parentPipelineId]; + + if (parentPipeline !== undefined) { + // remove sub-pipeline from parent pipeline + if (parentPipeline.processors !== undefined) { + const updatedProcessors = parentPipeline.processors.filter( + (p) => !(p.pipeline !== undefined && p.pipeline.name === pipelineName) + ); + // only update if we changed something + if (updatedProcessors.length !== parentPipeline.processors.length) { + const updatedPipeline: IngestPutPipelineRequest = { + ...parentPipeline, + id: parentPipelineId, + processors: updatedProcessors, + }; + + const updateResponse = await client.ingest.putPipeline(updatedPipeline); + if (updateResponse.acknowledged === true) { + response.updated = parentPipelineId; + } + } + } + } + } catch (error) { + // only suppress Not Found error + if (error.meta?.statusCode !== 404) { + throw error; + } + } + + // finally, delete pipeline + const deleteResponse = await client.ingest.deletePipeline({ id: pipelineName }); + if (deleteResponse.acknowledged === true) { + response.deleted = pipelineName; + } + + return response; +}; diff --git a/x-pack/plugins/enterprise_search/server/utils/create_pipeline_definitions.test.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts similarity index 91% rename from x-pack/plugins/enterprise_search/server/utils/create_pipeline_definitions.test.ts rename to x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts index 6961086edac1b..974d00fb6ae1d 100644 --- a/x-pack/plugins/enterprise_search/server/utils/create_pipeline_definitions.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts @@ -70,7 +70,7 @@ describe('formatMlPipelineBody util function', () => { model_id: modelId, target_field: `ml.inference.${destField}`, field_map: { - sourceField: modelInputField, + [sourceField]: modelInputField, }, }, }, @@ -113,20 +113,8 @@ describe('formatMlPipelineBody util function', () => { }); it('should raise an error if no model found', async () => { - const mockResponse = { - error: { - root_cause: [ - { - type: 'resource_not_found_exception', - reason: 'No known trained model with model_id [my-model-id]', - }, - ], - type: 'resource_not_found_exception', - reason: 'No known trained model with model_id [my-model-id]', - }, - status: 404, - }; - mockClient.ml.getTrainedModels.mockImplementation(() => Promise.resolve(mockResponse)); + const mockError = new Error('No known trained model with model_id [my-model-id]'); + mockClient.ml.getTrainedModels.mockImplementation(() => Promise.reject(mockError)); const asyncCall = formatMlPipelineBody( modelId, sourceField, @@ -154,7 +142,7 @@ describe('formatMlPipelineBody util function', () => { model_id: modelId, target_field: `ml.inference.${destField}`, field_map: { - sourceField: modelInputField, + [sourceField]: modelInputField, }, }, }, diff --git a/x-pack/plugins/enterprise_search/server/utils/create_pipeline_definitions.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts similarity index 97% rename from x-pack/plugins/enterprise_search/server/utils/create_pipeline_definitions.ts rename to x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts index 666588dd09886..2099f6bfc070f 100644 --- a/x-pack/plugins/enterprise_search/server/utils/create_pipeline_definitions.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts @@ -245,11 +245,8 @@ export const formatMlPipelineBody = async ( destinationField: string, esClient: ElasticsearchClient ): Promise => { + // this will raise a 404 if model doesn't exist const models = await esClient.ml.getTrainedModels({ model_id: modelId }); - // if we didn't find this model, we can't return anything useful - if (models.trained_model_configs === undefined || models.trained_model_configs.length === 0) { - throw new Error(`Couldn't find any trained models with id [${modelId}]`); - } const model = models.trained_model_configs[0]; // if model returned no input field, insert a placeholder const modelInputField = @@ -271,7 +268,7 @@ export const formatMlPipelineBody = async ( model_id: modelId, target_field: `ml.inference.${destinationField}`, field_map: { - sourceField: modelInputField, + [sourceField]: modelInputField, }, }, }, diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/get_custom_pipelines.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/get_custom_pipelines.ts new file mode 100644 index 0000000000000..11127e7e5d236 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/get_custom_pipelines.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { IngestGetPipelineResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { IScopedClusterClient } from '@kbn/core/server'; + +export const getCustomPipelines = async ( + indexName: string, + client: IScopedClusterClient +): Promise => { + try { + const pipelinesResponse = await client.asCurrentUser.ingest.getPipeline({ + id: `${indexName}*`, + }); + + return pipelinesResponse; + } catch (error) { + // If we can't find anything, we return an empty object + return {}; + } +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/get_default_pipeline.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/get_default_pipeline.ts new file mode 100644 index 0000000000000..0959b7e8cfa7f --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/get_default_pipeline.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 { IScopedClusterClient } from '@kbn/core/server'; + +import { CURRENT_CONNECTORS_INDEX } from '../..'; +import { DEFAULT_PIPELINE_VALUES } from '../../../common/constants'; + +import { IngestPipelineParams } from '../../../common/types/connectors'; +import { DefaultConnectorsPipelineMeta } from '../../index_management/setup_indices'; + +export const getDefaultPipeline = async ( + client: IScopedClusterClient +): Promise => { + const mapping = await client.asCurrentUser.indices.getMapping({ + index: CURRENT_CONNECTORS_INDEX, + }); + const meta: DefaultConnectorsPipelineMeta | undefined = + mapping[CURRENT_CONNECTORS_INDEX]?.mappings._meta?.pipeline; + const mappedMapping: IngestPipelineParams = meta + ? { + extract_binary_content: meta.default_extract_binary_content, + name: meta.default_name, + reduce_whitespace: meta.default_reduce_whitespace, + run_ml_inference: meta.default_run_ml_inference, + } + : DEFAULT_PIPELINE_VALUES; + return mappedMapping; +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/update_default_pipeline.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/update_default_pipeline.ts new file mode 100644 index 0000000000000..8dfaf0ac8ba42 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/update_default_pipeline.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 { IScopedClusterClient } from '@kbn/core/server'; + +import { CURRENT_CONNECTORS_INDEX } from '../..'; + +import { IngestPipelineParams } from '../../../common/types/connectors'; +import { + DefaultConnectorsPipelineMeta, + setupConnectorsIndices, +} from '../../index_management/setup_indices'; +import { isIndexNotFoundException } from '../../utils/identify_exceptions'; + +export const updateDefaultPipeline = async ( + client: IScopedClusterClient, + pipeline: IngestPipelineParams +) => { + try { + const mapping = await client.asCurrentUser.indices.getMapping({ + index: CURRENT_CONNECTORS_INDEX, + }); + const newPipeline: DefaultConnectorsPipelineMeta = { + default_extract_binary_content: pipeline.extract_binary_content, + default_name: pipeline.name, + default_reduce_whitespace: pipeline.reduce_whitespace, + default_run_ml_inference: pipeline.run_ml_inference, + }; + await client.asCurrentUser.indices.putMapping({ + _meta: { ...mapping[CURRENT_CONNECTORS_INDEX].mappings._meta, pipeline: newPipeline }, + index: CURRENT_CONNECTORS_INDEX, + }); + } catch (error) { + if (isIndexNotFoundException(error)) { + setupConnectorsIndices(client.asCurrentUser); + } + } +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/update_pipeline.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/update_pipeline.ts new file mode 100644 index 0000000000000..1acc91f7e2684 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/update_pipeline.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 { IScopedClusterClient } from '@kbn/core/server'; + +import { CONNECTORS_INDEX } from '../..'; + +import { IngestPipelineParams } from '../../../common/types/connectors'; + +export const updateConnectorPipeline = async ( + client: IScopedClusterClient, + connectorId: string, + pipeline: IngestPipelineParams +) => { + await client.asCurrentUser.update({ + doc: { pipeline }, + id: connectorId, + index: CONNECTORS_INDEX, + }); +}; diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts index 96f4ed8c2be53..7113f09b7ffe6 100644 --- a/x-pack/plugins/enterprise_search/server/plugin.ts +++ b/x-pack/plugins/enterprise_search/server/plugin.ts @@ -31,6 +31,7 @@ import { WORKPLACE_SEARCH_PLUGIN, ENTERPRISE_SEARCH_RELEVANCE_LOGS_SOURCE_ID, ENTERPRISE_SEARCH_AUDIT_LOGS_SOURCE_ID, + ENTERPRISE_SEARCH_ANALYTICS_LOGS_SOURCE_ID, } from '../common/constants'; import { registerTelemetryUsageCollector as registerASTelemetryUsageCollector } from './collectors/app_search/telemetry'; @@ -224,6 +225,14 @@ export class EnterpriseSearchPlugin implements Plugin { indexName: 'logs-enterprise_search*', }, }); + + infra.defineInternalSourceConfiguration(ENTERPRISE_SEARCH_ANALYTICS_LOGS_SOURCE_ID, { + name: 'Enterprise Search Behaviorial Analytics Logs', + logIndices: { + type: 'index_name', + indexName: 'logs-elastic_analytics.events-*', + }, + }); } public start() {} diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts index b17cbffd68c40..6c61e62ce0c66 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts @@ -6,14 +6,22 @@ */ import { schema } from '@kbn/config-schema'; + import { i18n } from '@kbn/i18n'; +import { ConnectorStatus } from '../../../common/types/connectors'; + import { ErrorCode } from '../../../common/types/error_codes'; import { addConnector } from '../../lib/connectors/add_connector'; import { fetchSyncJobsByConnectorId } from '../../lib/connectors/fetch_sync_jobs'; import { startConnectorSync } from '../../lib/connectors/start_sync'; import { updateConnectorConfiguration } from '../../lib/connectors/update_connector_configuration'; import { updateConnectorScheduling } from '../../lib/connectors/update_connector_scheduling'; +import { updateConnectorServiceType } from '../../lib/connectors/update_connector_service_type'; +import { updateConnectorStatus } from '../../lib/connectors/update_connector_status'; +import { getDefaultPipeline } from '../../lib/pipelines/get_default_pipeline'; +import { updateDefaultPipeline } from '../../lib/pipelines/update_default_pipeline'; +import { updateConnectorPipeline } from '../../lib/pipelines/update_pipeline'; import { RouteDependencies } from '../../plugin'; import { createError } from '../../utils/create_error'; @@ -104,11 +112,14 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) { params: schema.object({ connectorId: schema.string(), }), + body: schema.object({ + nextSyncConfig: schema.string(), + }), }, }, elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; - await startConnectorSync(client, request.params.connectorId); + await startConnectorSync(client, request.params.connectorId, request.body.nextSyncConfig); return response.ok(); }) ); @@ -137,4 +148,100 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) { return response.ok({ body: result }); }) ); + + router.put( + { + path: '/internal/enterprise_search/connectors/{connectorId}/pipeline', + validate: { + body: schema.object({ + extract_binary_content: schema.boolean(), + name: schema.string(), + reduce_whitespace: schema.boolean(), + run_ml_inference: schema.boolean(), + }), + params: schema.object({ + connectorId: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const { client } = (await context.core).elasticsearch; + + await updateConnectorPipeline(client, request.params.connectorId, request.body); + return response.ok(); + }) + ); + + router.put( + { + path: '/internal/enterprise_search/connectors/default_pipeline', + validate: { + body: schema.object({ + extract_binary_content: schema.boolean(), + name: schema.string(), + reduce_whitespace: schema.boolean(), + run_ml_inference: schema.boolean(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const { client } = (await context.core).elasticsearch; + await updateDefaultPipeline(client, request.body); + return response.ok(); + }) + ); + + router.get( + { + path: '/internal/enterprise_search/connectors/default_pipeline', + validate: {}, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const { client } = (await context.core).elasticsearch; + const result = await getDefaultPipeline(client); + return response.ok({ body: result }); + }) + ); + + router.put( + { + path: '/internal/enterprise_search/connectors/{connectorId}/service_type', + validate: { + params: schema.object({ + connectorId: schema.string(), + }), + body: schema.object({ serviceType: schema.string() }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const { client } = (await context.core).elasticsearch; + const result = await updateConnectorServiceType( + client, + request.params.connectorId, + request.body.serviceType + ); + return response.ok({ body: result }); + }) + ); + + router.put( + { + path: '/internal/enterprise_search/connectors/{connectorId}/status', + validate: { + params: schema.object({ + connectorId: schema.string(), + }), + body: schema.object({ status: schema.string() }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const { client } = (await context.core).elasticsearch; + const result = await updateConnectorStatus( + client, + request.params.connectorId, + request.body.status as ConnectorStatus + ); + return response.ok({ body: result }); + }) + ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts index 38e973f7305ad..0af97578b56a4 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts @@ -260,6 +260,16 @@ export function registerCrawlerRoutes(routeDependencies: RouteDependencies) { path: '/internal/enterprise_search/indices/{indexName}/crawler/domains/{domainId}', validate: { body: schema.object({ + auth: schema.maybe( + schema.nullable( + schema.object({ + header: schema.maybe(schema.string()), + password: schema.maybe(schema.string()), + type: schema.string(), + username: schema.maybe(schema.string()), + }) + ) + ), crawl_rules: schema.maybe( schema.arrayOf( schema.object({ diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts index f4c5657e000b1..c59b1081e86cb 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts @@ -9,10 +9,22 @@ import { MockRouter, mockDependencies } from '../../__mocks__'; import { RequestHandlerContext } from '@kbn/core/server'; +import { ErrorCode } from '../../../common/types/error_codes'; + jest.mock('../../lib/indices/fetch_ml_inference_pipeline_processors', () => ({ fetchMlInferencePipelineProcessors: jest.fn(), })); +jest.mock('../../utils/create_ml_inference_pipeline', () => ({ + createAndReferenceMlInferencePipeline: jest.fn(), +})); +jest.mock('../../lib/indices/delete_ml_inference_pipeline', () => ({ + deleteMlInferencePipeline: jest.fn(), +})); + +import { deleteMlInferencePipeline } from '../../lib/indices/delete_ml_inference_pipeline'; import { fetchMlInferencePipelineProcessors } from '../../lib/indices/fetch_ml_inference_pipeline_processors'; +import { createAndReferenceMlInferencePipeline } from '../../utils/create_ml_inference_pipeline'; +import { ElasticsearchResponseError } from '../../utils/identify_exceptions'; import { registerIndexRoutes } from './indices'; @@ -22,24 +34,24 @@ describe('Enterprise Search Managed Indices', () => { asCurrentUser: {}, }; - beforeEach(() => { - const context = { - core: Promise.resolve({ elasticsearch: { client: mockClient } }), - } as jest.Mocked; + describe('GET /internal/enterprise_search/indices/{indexName}/ml_inference/pipeline_processors', () => { + beforeEach(() => { + const context = { + core: Promise.resolve({ elasticsearch: { client: mockClient } }), + } as jest.Mocked; - mockRouter = new MockRouter({ - context, - method: 'get', - path: '/internal/enterprise_search/indices/{indexName}/ml_inference/pipeline_processors', - }); + mockRouter = new MockRouter({ + context, + method: 'get', + path: '/internal/enterprise_search/indices/{indexName}/ml_inference/pipeline_processors', + }); - registerIndexRoutes({ - ...mockDependencies, - router: mockRouter.router, + registerIndexRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); }); - }); - describe('GET /internal/enterprise_search/indices/{indexName}/ml_inference/pipeline_processors', () => { it('fails validation without index_name', () => { const request = { params: {} }; mockRouter.shouldThrow(request); @@ -71,4 +83,163 @@ describe('Enterprise Search Managed Indices', () => { }); }); }); + + describe('POST /internal/enterprise_search/indices/{indexName}/ml_inference/pipeline_processors', () => { + const mockRequestBody = { + model_id: 'my-model-id', + pipeline_name: 'my-pipeline-name', + source_field: 'my-source-field', + destination_field: 'my-dest-field', + }; + + beforeEach(() => { + jest.clearAllMocks(); + + const context = { + core: Promise.resolve({ elasticsearch: { client: mockClient } }), + } as jest.Mocked; + + mockRouter = new MockRouter({ + context, + method: 'post', + path: '/internal/enterprise_search/indices/{indexName}/ml_inference/pipeline_processors', + }); + + registerIndexRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('fails validation without index_name', () => { + const request = { + params: {}, + }; + mockRouter.shouldThrow(request); + }); + + it('fails validation without required body properties', () => { + const request = { + params: { indexName: 'my-index-name' }, + body: {}, + }; + mockRouter.shouldThrow(request); + }); + + it('creates an ML inference pipeline', async () => { + (createAndReferenceMlInferencePipeline as jest.Mock).mockImplementationOnce(() => { + return Promise.resolve({ + id: 'ml-inference-my-pipeline-name', + created: true, + addedToParentPipeline: true, + }); + }); + + await mockRouter.callRoute({ + params: { indexName: 'my-index-name' }, + body: mockRequestBody, + }); + + expect(createAndReferenceMlInferencePipeline).toHaveBeenCalledWith( + 'my-index-name', + mockRequestBody.pipeline_name, + mockRequestBody.model_id, + mockRequestBody.source_field, + mockRequestBody.destination_field, + {} + ); + + expect(mockRouter.response.ok).toHaveBeenCalledWith({ + body: { + created: 'ml-inference-my-pipeline-name', + }, + headers: { 'content-type': 'application/json' }, + }); + }); + + it('responds with 409 CONFLICT if the pipeline already exists', async () => { + (createAndReferenceMlInferencePipeline as jest.Mock).mockImplementationOnce(() => { + return Promise.reject(new Error(ErrorCode.PIPELINE_ALREADY_EXISTS)); + }); + + await mockRouter.callRoute({ + params: { indexName: 'my-index-name' }, + body: mockRequestBody, + }); + + expect(mockRouter.response.customError).toHaveBeenCalledWith( + expect.objectContaining({ + statusCode: 409, + }) + ); + }); + }); + + describe('DELETE /internal/enterprise_search/indices/{indexName}/ml_inference/pipelines/{pipelineName}', () => { + const indexName = 'my-index'; + const pipelineName = 'my-pipeline'; + + beforeEach(() => { + const context = { + core: Promise.resolve({ elasticsearch: { client: mockClient } }), + } as jest.Mocked; + + mockRouter = new MockRouter({ + context, + method: 'delete', + path: '/internal/enterprise_search/indices/{indexName}/ml_inference/pipelines/{pipelineName}', + }); + + registerIndexRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('fails validation without index_name', () => { + const request = { params: {} }; + mockRouter.shouldThrow(request); + }); + + it('deletes pipeline', async () => { + const mockResponse = { deleted: pipelineName }; + + (deleteMlInferencePipeline as jest.Mock).mockImplementationOnce(() => { + return Promise.resolve(mockResponse); + }); + + await mockRouter.callRoute({ + params: { indexName, pipelineName }, + }); + + expect(deleteMlInferencePipeline).toHaveBeenCalledWith(indexName, pipelineName, {}); + + expect(mockRouter.response.ok).toHaveBeenCalledWith({ + body: mockResponse, + headers: { 'content-type': 'application/json' }, + }); + }); + + it('raises error if deletion failed', async () => { + const errorReason = `pipeline is missing: [${pipelineName}]`; + const mockError = new Error(errorReason) as ElasticsearchResponseError; + mockError.meta = { + body: { + error: { + type: 'resource_not_found_exception', + }, + }, + }; + (deleteMlInferencePipeline as jest.Mock).mockImplementationOnce(() => { + return Promise.reject(mockError); + }); + + await mockRouter.callRoute({ + params: { indexName, pipelineName }, + }); + + expect(deleteMlInferencePipeline).toHaveBeenCalledWith(indexName, pipelineName, {}); + expect(mockRouter.response.customError).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts index aef5f944e6b98..088659d73dcfd 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts @@ -16,15 +16,24 @@ import { fetchConnectorByIndexName, fetchConnectors } from '../../lib/connectors import { fetchCrawlerByIndexName, fetchCrawlers } from '../../lib/crawler/fetch_crawlers'; import { createIndex } from '../../lib/indices/create_index'; +import { deleteMlInferencePipeline } from '../../lib/indices/delete_ml_inference_pipeline'; import { fetchIndex } from '../../lib/indices/fetch_index'; import { fetchIndices } from '../../lib/indices/fetch_indices'; import { fetchMlInferencePipelineProcessors } from '../../lib/indices/fetch_ml_inference_pipeline_processors'; import { generateApiKey } from '../../lib/indices/generate_api_key'; +import { createIndexPipelineDefinitions } from '../../lib/pipelines/create_pipeline_definitions'; +import { getCustomPipelines } from '../../lib/pipelines/get_custom_pipelines'; import { RouteDependencies } from '../../plugin'; import { createError } from '../../utils/create_error'; -import { createIndexPipelineDefinitions } from '../../utils/create_pipeline_definitions'; +import { + createAndReferenceMlInferencePipeline, + CreatedPipeline, +} from '../../utils/create_ml_inference_pipeline'; import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; -import { isIndexNotFoundException } from '../../utils/identify_exceptions'; +import { + isIndexNotFoundException, + isResourceNotFoundException, +} from '../../utils/identify_exceptions'; export function registerIndexRoutes({ router, @@ -266,6 +275,26 @@ export function registerIndexRoutes({ }) ); + router.get( + { + path: '/internal/enterprise_search/indices/{indexName}/pipelines', + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const indexName = decodeURIComponent(request.params.indexName); + const { client } = (await context.core).elasticsearch; + const pipelines = await getCustomPipelines(indexName, client); + return response.ok({ + body: pipelines, + headers: { 'content-type': 'application/json' }, + }); + }) + ); + router.get( { path: '/internal/enterprise_search/indices/{indexName}/ml_inference/pipeline_processors', @@ -291,6 +320,66 @@ export function registerIndexRoutes({ }) ); + router.post( + { + path: '/internal/enterprise_search/indices/{indexName}/ml_inference/pipeline_processors', + validate: { + params: schema.object({ + indexName: schema.string(), + }), + body: schema.object({ + pipeline_name: schema.string(), + model_id: schema.string(), + source_field: schema.string(), + destination_field: schema.maybe(schema.nullable(schema.string())), + }), + }, + }, + + elasticsearchErrorHandler(log, async (context, request, response) => { + const indexName = decodeURIComponent(request.params.indexName); + const { client } = (await context.core).elasticsearch; + const { + model_id: modelId, + pipeline_name: pipelineName, + source_field: sourceField, + destination_field: destinationField, + } = request.body; + + let createPipelineResult: CreatedPipeline | undefined; + try { + // Create the sub-pipeline for inference + createPipelineResult = await createAndReferenceMlInferencePipeline( + indexName, + pipelineName, + modelId, + sourceField, + destinationField || modelId, + client.asCurrentUser + ); + } catch (error) { + // Handle scenario where pipeline already exists + if ((error as Error).message === ErrorCode.PIPELINE_ALREADY_EXISTS) { + return createError({ + errorCode: (error as Error).message as ErrorCode, + message: 'Pipeline already exists', + response, + statusCode: 409, + }); + } + + throw error; + } + + return response.ok({ + body: { + created: createPipelineResult?.id, + }, + headers: { 'content-type': 'application/json' }, + }); + }) + ); + router.post( { path: '/internal/enterprise_search/indices', @@ -363,4 +452,46 @@ export function registerIndexRoutes({ }); }) ); + + router.delete( + { + path: '/internal/enterprise_search/indices/{indexName}/ml_inference/pipelines/{pipelineName}', + validate: { + params: schema.object({ + indexName: schema.string(), + pipelineName: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const indexName = decodeURIComponent(request.params.indexName); + const pipelineName = decodeURIComponent(request.params.pipelineName); + const { client } = (await context.core).elasticsearch; + + try { + const deleteResult = await deleteMlInferencePipeline( + indexName, + pipelineName, + client.asCurrentUser + ); + + return response.ok({ + body: deleteResult, + headers: { 'content-type': 'application/json' }, + }); + } catch (error) { + if (isResourceNotFoundException(error)) { + // return specific message if pipeline doesn't exist + return createError({ + errorCode: ErrorCode.RESOURCE_NOT_FOUND, + message: error.meta?.body?.error?.reason, + response, + statusCode: 404, + }); + } + // otherwise, let the default handler wrap it + throw error; + } + }) + ); } diff --git a/x-pack/plugins/enterprise_search/server/ui_settings.ts b/x-pack/plugins/enterprise_search/server/ui_settings.ts index 0497aa54d2eec..0be413f8d9c6a 100644 --- a/x-pack/plugins/enterprise_search/server/ui_settings.ts +++ b/x-pack/plugins/enterprise_search/server/ui_settings.ts @@ -9,12 +9,28 @@ import { schema } from '@kbn/config-schema'; import { UiSettingsParams } from '@kbn/core/types'; import { i18n } from '@kbn/i18n'; -import { enterpriseSearchFeatureId, enableIndexPipelinesTab } from '../common/ui_settings_keys'; +import { + enterpriseSearchFeatureId, + enableIndexPipelinesTab, + enableBehavioralAnalyticsSection, +} from '../common/ui_settings_keys'; /** * uiSettings definitions for Enterprise Search */ export const uiSettings: Record> = { + [enableBehavioralAnalyticsSection]: { + category: [enterpriseSearchFeatureId], + description: i18n.translate('xpack.enterpriseSearch.uiSettings.analytics.description', { + defaultMessage: 'Enable the new Analytics section in Enterprise Search.', + }), + name: i18n.translate('xpack.enterpriseSearch.uiSettings.analytics.name', { + defaultMessage: 'Enable Behavioral Analytics', + }), + requiresPageReload: true, + schema: schema.boolean(), + value: false, + }, [enableIndexPipelinesTab]: { category: [enterpriseSearchFeatureId], description: i18n.translate('xpack.enterpriseSearch.uiSettings.indexPipelines.description', { diff --git a/x-pack/plugins/enterprise_search/server/utils/create_ml_inference_pipeline.test.ts b/x-pack/plugins/enterprise_search/server/utils/create_ml_inference_pipeline.test.ts new file mode 100644 index 0000000000000..e4100a003eb81 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/create_ml_inference_pipeline.test.ts @@ -0,0 +1,187 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { ElasticsearchClient } from '@kbn/core/server'; + +import { + createMlInferencePipeline, + addSubPipelineToIndexSpecificMlPipeline, +} from './create_ml_inference_pipeline'; + +describe('createMlInferencePipeline util function', () => { + const pipelineName = 'my-pipeline'; + const modelId = 'my-model-id'; + const sourceField = 'my-source-field'; + const destinationField = 'my-dest-field'; + const inferencePipelineGeneratedName = `ml-inference-${pipelineName}`; + + const mockClient = { + ingest: { + getPipeline: jest.fn(), + putPipeline: jest.fn(), + }, + ml: { + getTrainedModels: jest.fn(), + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should create the pipeline if it doesn't exist", async () => { + mockClient.ingest.getPipeline.mockImplementation(() => Promise.reject({ statusCode: 404 })); // Pipeline does not exist + mockClient.ingest.putPipeline.mockImplementation(() => Promise.resolve({ acknowledged: true })); + mockClient.ml.getTrainedModels.mockImplementation(() => + Promise.resolve({ + trained_model_configs: [ + { + input: { + field_names: ['target-field'], + }, + }, + ], + }) + ); + + const expectedResult = { + created: true, + id: inferencePipelineGeneratedName, + }; + + const actualResult = await createMlInferencePipeline( + pipelineName, + modelId, + sourceField, + destinationField, + mockClient as unknown as ElasticsearchClient + ); + + expect(actualResult).toEqual(expectedResult); + expect(mockClient.ingest.putPipeline).toHaveBeenCalled(); + }); + + it('should throw an error without creating the pipeline if it already exists', () => { + mockClient.ingest.getPipeline.mockImplementation(() => + Promise.resolve({ + [inferencePipelineGeneratedName]: {}, + }) + ); // Pipeline exists + + const actualResult = createMlInferencePipeline( + pipelineName, + modelId, + sourceField, + destinationField, + mockClient as unknown as ElasticsearchClient + ); + + expect(actualResult).rejects.toThrow(Error); + expect(mockClient.ingest.putPipeline).not.toHaveBeenCalled(); + }); +}); + +describe('addSubPipelineToIndexSpecificMlPipeline util function', () => { + const indexName = 'my-index'; + const parentPipelineId = `${indexName}@ml-inference`; + const pipelineName = 'ml-inference-my-pipeline'; + + const mockClient = { + ingest: { + getPipeline: jest.fn(), + putPipeline: jest.fn(), + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should add the sub-pipeline reference to the parent ML pipeline if it isn't there", async () => { + mockClient.ingest.getPipeline.mockImplementation(() => + Promise.resolve({ + [parentPipelineId]: { + processors: [], + }, + }) + ); + + const expectedResult = { + id: pipelineName, + addedToParentPipeline: true, + }; + + const actualResult = await addSubPipelineToIndexSpecificMlPipeline( + indexName, + pipelineName, + mockClient as unknown as ElasticsearchClient + ); + + expect(actualResult).toEqual(expectedResult); + // Verify the parent pipeline was updated with a reference of the sub-pipeline + expect(mockClient.ingest.putPipeline).toHaveBeenCalledWith({ + id: parentPipelineId, + processors: expect.arrayContaining([ + { + pipeline: { + name: pipelineName, + }, + }, + ]), + }); + }); + + it('should not add the sub-pipeline reference to the parent ML pipeline if the parent is missing', async () => { + mockClient.ingest.getPipeline.mockImplementation(() => Promise.reject({ statusCode: 404 })); // Pipeline does not exist + + const expectedResult = { + id: pipelineName, + addedToParentPipeline: false, + }; + + const actualResult = await addSubPipelineToIndexSpecificMlPipeline( + indexName, + pipelineName, + mockClient as unknown as ElasticsearchClient + ); + + expect(actualResult).toEqual(expectedResult); + // Verify the parent pipeline was NOT updated + expect(mockClient.ingest.putPipeline).not.toHaveBeenCalled(); + }); + + it('should not add the sub-pipeline reference to the parent ML pipeline if it is already there', async () => { + mockClient.ingest.getPipeline.mockImplementation(() => + Promise.resolve({ + [parentPipelineId]: { + processors: [ + { + pipeline: { + name: pipelineName, + }, + }, + ], + }, + }) + ); + + const expectedResult = { + id: pipelineName, + addedToParentPipeline: false, + }; + + const actualResult = await addSubPipelineToIndexSpecificMlPipeline( + indexName, + pipelineName, + mockClient as unknown as ElasticsearchClient + ); + + expect(actualResult).toEqual(expectedResult); + // Verify the parent pipeline was NOT updated + expect(mockClient.ingest.putPipeline).not.toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/utils/create_ml_inference_pipeline.ts b/x-pack/plugins/enterprise_search/server/utils/create_ml_inference_pipeline.ts new file mode 100644 index 0000000000000..3e1b51efabaf8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/create_ml_inference_pipeline.ts @@ -0,0 +1,172 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { IngestGetPipelineResponse, IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; +import { ElasticsearchClient } from '@kbn/core/server'; + +import { ErrorCode } from '../../common/types/error_codes'; + +import { formatMlPipelineBody } from '../lib/pipelines/create_pipeline_definitions'; + +/** + * Details of a created pipeline. + */ +export interface CreatedPipeline { + id: string; + created?: boolean; + addedToParentPipeline?: boolean; +} + +/** + * Creates a Machine Learning Inference pipeline with the given settings, if it doesn't exist yet, + * then references it in the "parent" ML Inference pipeline that is associated with the index. + * @param indexName name of the index this pipeline corresponds to. + * @param pipelineName pipeline name set by the user. + * @param modelId model ID selected by the user. + * @param sourceField The document field that model will read. + * @param destinationField The document field that the model will write to. + * @param esClient the Elasticsearch Client to use when retrieving pipeline and model details. + */ +export const createAndReferenceMlInferencePipeline = async ( + indexName: string, + pipelineName: string, + modelId: string, + sourceField: string, + destinationField: string, + esClient: ElasticsearchClient +): Promise => { + const createPipelineResult = await createMlInferencePipeline( + pipelineName, + modelId, + sourceField, + destinationField || modelId, + esClient + ); + + const addSubPipelineResult = await addSubPipelineToIndexSpecificMlPipeline( + indexName, + createPipelineResult.id, + esClient + ); + + return Promise.resolve({ + ...createPipelineResult, + addedToParentPipeline: addSubPipelineResult.addedToParentPipeline, + }); +}; + +/** + * Creates a Machine Learning Inference pipeline with the given settings, if it doesn't exist yet. + * @param pipelineName pipeline name set by the user. + * @param modelId model ID selected by the user. + * @param sourceField The document field that model will read. + * @param destinationField The document field that the model will write to. + * @param esClient the Elasticsearch Client to use when retrieving pipeline and model details. + */ +export const createMlInferencePipeline = async ( + pipelineName: string, + modelId: string, + sourceField: string, + destinationField: string, + esClient: ElasticsearchClient +): Promise => { + const inferencePipelineGeneratedName = `ml-inference-${pipelineName}`; + + // Check that a pipeline with the same name doesn't already exist + let pipelineByName: IngestGetPipelineResponse | undefined; + try { + pipelineByName = await esClient.ingest.getPipeline({ + id: inferencePipelineGeneratedName, + }); + } catch (error) { + // Silently swallow error + } + if (pipelineByName?.[inferencePipelineGeneratedName]) { + throw new Error(ErrorCode.PIPELINE_ALREADY_EXISTS); + } + + // Generate pipeline with default processors + const mlInferencePipeline = await formatMlPipelineBody( + modelId, + sourceField, + destinationField, + esClient + ); + + await esClient.ingest.putPipeline({ + id: inferencePipelineGeneratedName, + ...mlInferencePipeline, + }); + + return Promise.resolve({ + id: inferencePipelineGeneratedName, + created: true, + }); +}; + +/** + * Adds the supplied a Machine Learning Inference pipeline reference to the "parent" ML Inference + * pipeline that is associated with the index. + * @param indexName name of the index this pipeline corresponds to. + * @param pipelineName name of the ML Inference pipeline to add. + * @param esClient the Elasticsearch Client to use when retrieving pipeline details. + */ +export const addSubPipelineToIndexSpecificMlPipeline = async ( + indexName: string, + pipelineName: string, + esClient: ElasticsearchClient +): Promise => { + const parentPipelineId = `${indexName}@ml-inference`; + + // Fetch the parent pipeline + let parentPipeline: IngestPipeline | undefined; + try { + const pipelineResponse = await esClient.ingest.getPipeline({ + id: parentPipelineId, + }); + parentPipeline = pipelineResponse[parentPipelineId]; + } catch (error) { + // Swallow error; in this case the next step will return + } + + // Verify the parent pipeline exists with a processors array + if (!parentPipeline?.processors) { + return Promise.resolve({ + id: pipelineName, + addedToParentPipeline: false, + }); + } + + // Check if the sub-pipeline reference is already in the list of processors, + // if so, don't modify it + const existingSubPipeline = parentPipeline.processors.find( + (p) => p.pipeline?.name === pipelineName + ); + if (existingSubPipeline) { + return Promise.resolve({ + id: pipelineName, + addedToParentPipeline: false, + }); + } + + // Add sub-processor to the ML inference parent pipeline + parentPipeline.processors.push({ + pipeline: { + name: pipelineName, + }, + }); + + await esClient.ingest.putPipeline({ + id: parentPipelineId, + ...parentPipeline, + }); + + return Promise.resolve({ + id: pipelineName, + addedToParentPipeline: true, + }); +}; diff --git a/x-pack/plugins/enterprise_search/server/utils/identify_exceptions.ts b/x-pack/plugins/enterprise_search/server/utils/identify_exceptions.ts index 7bc13135343dc..d65f2918275d2 100644 --- a/x-pack/plugins/enterprise_search/server/utils/identify_exceptions.ts +++ b/x-pack/plugins/enterprise_search/server/utils/identify_exceptions.ts @@ -5,7 +5,7 @@ * 2.0. */ -interface ElasticsearchResponseError { +export interface ElasticsearchResponseError { meta?: { body?: { error?: { @@ -23,5 +23,8 @@ export const isIndexNotFoundException = (error: ElasticsearchResponseError) => export const isResourceAlreadyExistsException = (error: ElasticsearchResponseError) => error?.meta?.body?.error?.type === 'resource_already_exists_exception'; +export const isResourceNotFoundException = (error: ElasticsearchResponseError) => + error?.meta?.body?.error?.type === 'resource_not_found_exception'; + export const isUnauthorizedException = (error: ElasticsearchResponseError) => error.meta?.statusCode === 403; diff --git a/x-pack/plugins/fleet/common/errors.ts b/x-pack/plugins/fleet/common/errors.ts new file mode 100644 index 0000000000000..8e22b971be6ad --- /dev/null +++ b/x-pack/plugins/fleet/common/errors.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. + */ +/* eslint-disable max-classes-per-file */ + +import type { FleetErrorType } from './types'; + +export class FleetError extends Error { + attributes?: { type: FleetErrorType }; + constructor(message?: string, public readonly meta?: unknown) { + super(message); + this.name = this.constructor.name; // for stack traces + } +} + +export class PackagePolicyValidationError extends FleetError {} diff --git a/x-pack/plugins/fleet/common/experimental_features.ts b/x-pack/plugins/fleet/common/experimental_features.ts index 8367b09513716..8926a1092fca2 100644 --- a/x-pack/plugins/fleet/common/experimental_features.ts +++ b/x-pack/plugins/fleet/common/experimental_features.ts @@ -14,6 +14,7 @@ export type ExperimentalFeatures = typeof allowedExperimentalValues; export const allowedExperimentalValues = Object.freeze({ createPackagePolicyMultiPageLayout: true, packageVerification: true, + showDevtoolsRequest: true, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/fleet/common/index.ts b/x-pack/plugins/fleet/common/index.ts index b6107a769793c..d9e3fd8b9ceb6 100644 --- a/x-pack/plugins/fleet/common/index.ts +++ b/x-pack/plugins/fleet/common/index.ts @@ -60,6 +60,7 @@ export { fleetSetupRouteService, // Package policy helpers isValidNamespace, + INVALID_NAMESPACE_CHARACTERS, // TODO Should probably not be exposed by Fleet decodeCloudId, } from './services'; diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 156adc5bef780..60c4dd7fd8975 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -2131,6 +2131,14 @@ }, { "$ref": "#/components/parameters/kuery" + }, + { + "schema": { + "type": "boolean" + }, + "in": "query", + "name": "full", + "description": "When set to true, retrieve the related package policies for each agent policy." } ], "description": "" @@ -4903,21 +4911,12 @@ "inactive" ] }, - "packagePolicies": { - "oneOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "array", - "items": { - "$ref": "#/components/schemas/package_policy" - } - } - ] + "package_policies": { + "description": "This field is present only when retrieving a single agent policy, or when retrieving a list of agent policy with the ?full=true parameter", + "type": "array", + "items": { + "$ref": "#/components/schemas/package_policy" + } }, "updated_on": { "type": "string", diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index db75255d88f5a..567ee06825b92 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -1312,6 +1312,13 @@ paths: - $ref: '#/components/parameters/page_size' - $ref: '#/components/parameters/page_index' - $ref: '#/components/parameters/kuery' + - schema: + type: boolean + in: query + name: full + description: >- + When set to true, retrieve the related package policies for each + agent policy/ description: '' post: summary: Agent policy - Create @@ -3107,14 +3114,14 @@ components: enum: - active - inactive - packagePolicies: - oneOf: - - type: array - items: - type: string - - type: array - items: - $ref: '#/components/schemas/package_policy' + package_policies: + description: >- + This field is present only when retrieving a single agent + policy, or when retrieving a list of agent policy with the + ?full=true parameter + type: array + items: + $ref: '#/components/schemas/package_policy' updated_on: type: string format: date-time diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy.yaml index c2cebb183ed86..b3895ed2627f7 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy.yaml @@ -9,14 +9,11 @@ allOf: enum: - active - inactive - packagePolicies: - oneOf: - - type: array - items: - type: string - - type: array - items: - $ref: ./package_policy.yaml + package_policies: + description: This field is present only when retrieving a single agent policy, or when retrieving a list of agent policy with the ?full=true parameter + type: array + items: + $ref: ./package_policy.yaml updated_on: type: string format: date-time diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml index b075d42d34af9..d431024445656 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies.yaml @@ -29,6 +29,12 @@ get: - $ref: ../components/parameters/page_size.yaml - $ref: ../components/parameters/page_index.yaml - $ref: ../components/parameters/kuery.yaml + - schema: + type: boolean + in: query + name: full + description: When set to true, retrieve the related package policies for each agent policy. + description: '' post: summary: Agent policy - Create diff --git a/x-pack/plugins/fleet/server/services/package_policies/__snapshots__/simplified_package_policy_helper.test.ts.snap b/x-pack/plugins/fleet/common/services/__snapshots__/simplified_package_policy_helper.test.ts.snap similarity index 100% rename from x-pack/plugins/fleet/server/services/package_policies/__snapshots__/simplified_package_policy_helper.test.ts.snap rename to x-pack/plugins/fleet/common/services/__snapshots__/simplified_package_policy_helper.test.ts.snap diff --git a/x-pack/plugins/fleet/common/services/index.ts b/x-pack/plugins/fleet/common/services/index.ts index f8e59ef6afee9..8261ccb3f82c0 100644 --- a/x-pack/plugins/fleet/common/services/index.ts +++ b/x-pack/plugins/fleet/common/services/index.ts @@ -15,7 +15,7 @@ export { export { fullAgentPolicyToYaml } from './full_agent_policy_to_yaml'; export { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from './limited_package'; export { decodeCloudId } from './decode_cloud_id'; -export { isValidNamespace } from './is_valid_namespace'; +export { isValidNamespace, INVALID_NAMESPACE_CHARACTERS } from './is_valid_namespace'; export { isDiffPathProtocol } from './is_diff_path_protocol'; export { LicenseService } from './license'; export { isAgentUpgradeable } from './is_agent_upgradeable'; diff --git a/x-pack/plugins/fleet/common/services/is_valid_namespace.ts b/x-pack/plugins/fleet/common/services/is_valid_namespace.ts index 47f9c3b96c34b..359f8069c2046 100644 --- a/x-pack/plugins/fleet/common/services/is_valid_namespace.ts +++ b/x-pack/plugins/fleet/common/services/is_valid_namespace.ts @@ -25,7 +25,7 @@ export function isValidNamespace(namespace: string): { valid: boolean; error?: s defaultMessage: 'Namespace must be lowercase', }), }; - } else if (/[\*\\/\?"<>|\s,#:-]+/.test(namespace)) { + } else if (INVALID_NAMESPACE_CHARACTERS.test(namespace)) { return { valid: false, error: i18n.translate('xpack.fleet.namespaceValidation.invalidCharactersErrorMessage', { @@ -48,3 +48,5 @@ export function isValidNamespace(namespace: string): { valid: boolean; error?: s return { valid: true }; } + +export const INVALID_NAMESPACE_CHARACTERS = /[\*\\/\?"<>|\s,#:-]+/; diff --git a/x-pack/plugins/fleet/server/services/package_policies/simplified_package_policy_helper.test.ts b/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.test.ts similarity index 94% rename from x-pack/plugins/fleet/server/services/package_policies/simplified_package_policy_helper.test.ts rename to x-pack/plugins/fleet/common/services/simplified_package_policy_helper.test.ts index 15fd7c902b02e..eec59a647c39d 100644 --- a/x-pack/plugins/fleet/server/services/package_policies/simplified_package_policy_helper.test.ts +++ b/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.test.ts @@ -5,13 +5,14 @@ * 2.0. */ -import type { NewPackagePolicy, PackageInfo } from '../../types'; +import type { NewPackagePolicy, PackageInfo } from '../../server/types'; + +import nginxPackageInfo from '../../server/services/package_policies/fixtures/package_info/nginx_1.5.0.json'; import { simplifiedPackagePolicytoNewPackagePolicy, generateInputId, } from './simplified_package_policy_helper'; -import nginxPackageInfo from './fixtures/package_info/nginx_1.5.0.json'; function getEnabledInputsAndStreams(newPackagePolicy: NewPackagePolicy) { return newPackagePolicy.inputs diff --git a/x-pack/plugins/fleet/server/services/package_policies/simplified_package_policy_helper.ts b/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.ts similarity index 88% rename from x-pack/plugins/fleet/server/services/package_policies/simplified_package_policy_helper.ts rename to x-pack/plugins/fleet/common/services/simplified_package_policy_helper.ts index 12db049aef71e..c0d05d1eeab2a 100644 --- a/x-pack/plugins/fleet/server/services/package_policies/simplified_package_policy_helper.ts +++ b/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.ts @@ -5,16 +5,26 @@ * 2.0. */ -import { packageToPackagePolicy } from '../../../common/services'; import type { NewPackagePolicyInput, NewPackagePolicyInputStream, PackagePolicyConfigRecord, -} from '../../../common/types'; -import { PackagePolicyValidationError } from '../../errors'; -import type { NewPackagePolicy, PackageInfo } from '../../types'; + NewPackagePolicy, + PackageInfo, +} from '../types'; +import { PackagePolicyValidationError } from '../errors'; -type SimplifiedVars = Record; +import { packageToPackagePolicy } from '.'; + +export type SimplifiedVars = Record; + +export type SimplifiedPackagePolicyStreams = Record< + string, + { + enabled?: undefined | boolean; + vars?: SimplifiedVars; + } +>; export interface SimplifiedPackagePolicy { id?: string; @@ -28,13 +38,7 @@ export interface SimplifiedPackagePolicy { { enabled?: boolean | undefined; vars?: SimplifiedVars; - streams?: Record< - string, - { - enabled?: undefined | boolean; - vars?: SimplifiedVars; - } - >; + streams?: SimplifiedPackagePolicyStreams; } >; } diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index ffcc8361c1235..b2742b0d36886 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -48,7 +48,13 @@ export type InstallSource = 'registry' | 'upload' | 'bundled'; export type EpmPackageInstallStatus = 'installed' | 'installing' | 'install_failed'; -export type DetailViewPanelName = 'overview' | 'policies' | 'assets' | 'settings' | 'custom'; +export type DetailViewPanelName = + | 'overview' + | 'policies' + | 'assets' + | 'settings' + | 'custom' + | 'api-reference'; export type ServiceName = 'kibana' | 'elasticsearch'; export type AgentAssetType = typeof agentAssetTypes; export type DocAssetType = 'doc' | 'notice'; 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 51a8826456fc6..cc49d03f1f1c6 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 @@ -43,12 +43,21 @@ import { useGetPackageInfoByKey, sendCreateAgentPolicy, } from '../../../../hooks'; -import { Loading, Error, ExtensionWrapper } from '../../../../components'; +import { + Loading, + Error, + ExtensionWrapper, + DevtoolsRequestFlyoutButton, +} from '../../../../components'; import { agentPolicyFormValidation, ConfirmDeployAgentPolicyModal } from '../../components'; import { useUIExtension } from '../../../../hooks'; import type { PackagePolicyEditExtensionComponentProps } from '../../../../types'; -import { pkgKeyFromPackageInfo, isVerificationError } from '../../../../services'; +import { + pkgKeyFromPackageInfo, + isVerificationError, + ExperimentalFeaturesService, +} from '../../../../services'; import type { PackagePolicyFormState, @@ -60,13 +69,13 @@ import { IntegrationBreadcrumb } from '../components'; import type { PackagePolicyValidationResults } from '../services'; import { validatePackagePolicy, validationHasErrors } from '../services'; - import { StepConfigurePackagePolicy, StepDefinePackagePolicy, SelectedPolicyTab, StepSelectHosts, } from '../components'; +import { generateCreatePackagePolicyDevToolsRequest } from '../../services'; import { CreatePackagePolicySinglePageLayout, PostInstallAddAgentModal } from './components'; @@ -548,6 +557,15 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ }, ]; + const { showDevtoolsRequest } = ExperimentalFeaturesService.get(); + const devtoolRequest = useMemo( + () => + generateCreatePackagePolicyDevToolsRequest({ + ...packagePolicy, + }), + [packagePolicy] + ); + // Display package error if there is one if (packageInfoError) { return ( @@ -562,6 +580,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ /> ); } + return ( @@ -617,6 +636,22 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ />
+ {showDevtoolsRequest ? ( + + + + ) : null} onSubmit()} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx index 87614fc6413c8..c23f29a81c395 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx @@ -5,9 +5,10 @@ * 2.0. */ -import React, { memo, useState } from 'react'; +import React, { memo, useMemo, useState } from 'react'; import { useHistory } from 'react-router-dom'; import styled from 'styled-components'; +import { pick } from 'lodash'; import { EuiBottomBar, EuiFlexGroup, @@ -35,6 +36,9 @@ import { agentPolicyFormValidation, ConfirmDeployAgentPolicyModal, } from '../../../components'; +import { DevtoolsRequestFlyoutButton } from '../../../../../components'; +import { ExperimentalFeaturesService } from '../../../../../services'; +import { generateUpdateAgentPolicyDevToolsRequest } from '../../../services'; const FormWrapper = styled.div` max-width: 800px; @@ -126,6 +130,26 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( setIsLoading(false); }; + const { showDevtoolsRequest } = ExperimentalFeaturesService.get(); + const devtoolRequest = useMemo( + () => + generateUpdateAgentPolicyDevToolsRequest( + agentPolicy.id, + pick( + agentPolicy, + 'name', + 'description', + 'namespace', + 'monitoring_enabled', + 'unenroll_timeout', + 'data_output_id', + 'monitoring_output_id', + 'download_source_id' + ) + ), + [agentPolicy] + ); + const onSubmit = async () => { // Retrieve agent count if fleet is enabled if (isFleetEnabled) { @@ -197,6 +221,23 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( /> + {showDevtoolsRequest ? ( + + 0} + btnProps={{ + color: 'ghost', + }} + description={i18n.translate( + 'xpack.fleet.editAgentPolicy.devtoolsRequestDescription', + { + defaultMessage: 'This Kibana request updates an agent policy.', + } + )} + request={devtoolRequest} + /> + + ) : null} + generateUpdatePackagePolicyDevToolsRequest( + packagePolicyId, + omit(packagePolicy, 'elasticsearch') + ), + [packagePolicyId, packagePolicy] + ); + return ( @@ -638,6 +656,23 @@ export const EditPackagePolicyForm = memo<{ /> + {showDevtoolsRequest ? ( + + + + ) : null} props.theme.eui.euiZLevel5}; @@ -98,6 +101,11 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent = ({ /> ); + const { showDevtoolsRequest } = ExperimentalFeaturesService.get(); + const agentPolicyContent = useMemo( + () => generateCreateAgentPolicyDevToolsRequest(agentPolicy, withSysMonitoring), + [agentPolicy, withSysMonitoring] + ); const footer = ( @@ -111,48 +119,68 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent = ({ - 0} - onClick={async () => { - setIsLoading(true); - try { - const { data, error } = await createAgentPolicy(); - setIsLoading(false); - if (data) { - notifications.toasts.addSuccess( - i18n.translate('xpack.fleet.createAgentPolicy.successNotificationTitle', { - defaultMessage: "Agent policy '{name}' created", - values: { name: agentPolicy.name }, - }) - ); - onClose(data.item); - } else { - notifications.toasts.addDanger( - error - ? error.message - : i18n.translate('xpack.fleet.createAgentPolicy.errorNotificationTitle', { - defaultMessage: 'Unable to create agent policy', - }) - ); + + {showDevtoolsRequest ? ( + + 0} + description={i18n.translate( + 'xpack.fleet.createAgentPolicy.devtoolsRequestDescription', + { + defaultMessage: 'This Kibana request creates a new agent policy.', + } + )} + request={agentPolicyContent} + /> + + ) : null} + + 0 } - } catch (e) { - setIsLoading(false); - notifications.toasts.addDanger( - i18n.translate('xpack.fleet.createAgentPolicy.errorNotificationTitle', { - defaultMessage: 'Unable to create agent policy', - }) - ); - } - }} - data-test-subj="createAgentPolicyFlyoutBtn" - > - - + onClick={async () => { + setIsLoading(true); + try { + const { data, error } = await createAgentPolicy(); + setIsLoading(false); + if (data) { + notifications.toasts.addSuccess( + i18n.translate('xpack.fleet.createAgentPolicy.successNotificationTitle', { + defaultMessage: "Agent policy '{name}' created", + values: { name: agentPolicy.name }, + }) + ); + onClose(data.item); + } else { + notifications.toasts.addDanger( + error + ? error.message + : i18n.translate('xpack.fleet.createAgentPolicy.errorNotificationTitle', { + defaultMessage: 'Unable to create agent policy', + }) + ); + } + } catch (e) { + setIsLoading(false); + notifications.toasts.addDanger( + i18n.translate('xpack.fleet.createAgentPolicy.errorNotificationTitle', { + defaultMessage: 'Unable to create agent policy', + }) + ); + } + }} + data-test-subj="createAgentPolicyFlyoutBtn" + > + + + +
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/services/devtools_request.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/services/devtools_request.tsx new file mode 100644 index 0000000000000..875d43d1f0ea4 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/services/devtools_request.tsx @@ -0,0 +1,146 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { omit } from 'lodash'; + +import { agentPolicyRouteService, packagePolicyRouteService } from '../../../services'; +import { generateInputId } from '../../../../../../common/services/simplified_package_policy_helper'; +import type { + SimplifiedPackagePolicy, + SimplifiedVars, + SimplifiedPackagePolicyStreams, +} from '../../../../../../common/services/simplified_package_policy_helper'; +import type { + NewAgentPolicy, + NewPackagePolicy, + UpdatePackagePolicy, + UpdateAgentPolicyRequest, +} from '../../../types'; + +function generateKibanaDevToolsRequest(method: string, path: string, body: any) { + return `${method} kbn:${path}\n${JSON.stringify(body, null, 2)}\n`; +} + +/** + * Generate a request to create an agent policy that can be used in Kibana Dev tools + * @param agentPolicy + * @param withSysMonitoring + * @returns + */ +export function generateCreateAgentPolicyDevToolsRequest( + agentPolicy: NewAgentPolicy, + withSysMonitoring?: boolean +) { + return generateKibanaDevToolsRequest( + 'POST', + `${agentPolicyRouteService.getCreatePath()}${withSysMonitoring ? '?sys_monitoring=true' : ''}`, + agentPolicy + ); +} + +/** + * Generate a request to create a package policy that can be used in Kibana Dev tools + * @param packagePolicy + * @param withSysMonitoring + * @returns + */ +export function generateCreatePackagePolicyDevToolsRequest( + packagePolicy: NewPackagePolicy & { force?: boolean } +) { + return generateKibanaDevToolsRequest('POST', packagePolicyRouteService.getCreatePath(), { + policy_id: packagePolicy.policy_id ? packagePolicy.policy_id : '', + package: formatPackage(packagePolicy.package), + ...omit(packagePolicy, 'policy_id', 'package', 'enabled'), + inputs: formatInputs(packagePolicy.inputs), + vars: formatVars(packagePolicy.vars), + }); +} + +/** + * Generate a request to update a package policy that can be used in Kibana Dev tools + * @param packagePolicyId + * @param packagePolicy + * @returns + */ +export function generateUpdatePackagePolicyDevToolsRequest( + packagePolicyId: string, + packagePolicy: UpdatePackagePolicy +) { + return generateKibanaDevToolsRequest( + 'PUT', + packagePolicyRouteService.getUpdatePath(packagePolicyId), + { + package: formatPackage(packagePolicy.package), + ...omit(packagePolicy, 'version', 'package', 'enabled'), + inputs: formatInputs(packagePolicy.inputs), + vars: formatVars(packagePolicy.vars), + } + ); +} + +/** + * Generate a request to update an agent policy that can be used in Kibana Dev tools + * @param agentPolicyId + * @param agentPolicy + * @returns + */ +export function generateUpdateAgentPolicyDevToolsRequest( + agentPolicyId: string, + agentPolicy: UpdateAgentPolicyRequest['body'] +) { + return generateKibanaDevToolsRequest( + 'PUT', + agentPolicyRouteService.getUpdatePath(agentPolicyId), + omit(agentPolicy, 'version') + ); +} + +function formatVars(vars: NewPackagePolicy['inputs'][number]['vars']) { + if (!vars) { + return; + } + + return Object.entries(vars).reduce((acc, [varKey, varRecord]) => { + acc[varKey] = varRecord.value; + + return acc; + }, {} as SimplifiedVars); +} + +function formatInputs(inputs: NewPackagePolicy['inputs']) { + return inputs.reduce((acc, input) => { + const inputId = generateInputId(input); + if (!acc) { + acc = {}; + } + acc[inputId] = { + enabled: input.enabled, + vars: formatVars(input.vars), + streams: formatStreams(input.streams), + }; + + return acc; + }, {} as SimplifiedPackagePolicy['inputs']); +} + +function formatStreams(streams: NewPackagePolicy['inputs'][number]['streams']) { + return streams.reduce((acc, stream) => { + if (!acc) { + acc = {}; + } + acc[stream.data_stream.dataset] = { + enabled: stream.enabled, + vars: formatVars(stream.vars), + }; + + return acc; + }, {} as SimplifiedPackagePolicyStreams); +} + +function formatPackage(pkg: NewPackagePolicy['package']) { + return omit(pkg, 'title'); +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/services/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/services/index.tsx new file mode 100644 index 0000000000000..25266fb5326c7 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/services/index.tsx @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { + generateCreateAgentPolicyDevToolsRequest, + generateCreatePackagePolicyDevToolsRequest, + generateUpdatePackagePolicyDevToolsRequest, + generateUpdateAgentPolicyDevToolsRequest, +} from './devtools_request'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_button.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_button.tsx index 409a096cf9fe2..adbf4d2bc3141 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_button.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_button.tsx @@ -61,7 +61,7 @@ export const AgentActivityButton: React.FC<{ } isStepOpen={agentActivityTourState.isOpen} - onFinish={() => setAgentActivityTourState({ isOpen: false })} + onFinish={onFinish} minWidth={360} maxWidth={360} step={1} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/documentation/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/documentation/index.tsx new file mode 100644 index 0000000000000..d90067d3f591d --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/documentation/index.tsx @@ -0,0 +1,270 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { + EuiAccordion, + EuiBasicTable, + EuiCode, + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiSpacer, + EuiText, + EuiLink, + EuiBetaBadge, +} from '@elastic/eui'; +import type { EuiInMemoryTableProps } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import type { + PackageInfo, + RegistryVarsEntry, + RegistryStream, + RegistryInput, +} from '../../../../../types'; +import { useStartServices } from '../../../../../../../hooks'; +import { getStreamsForInputType } from '../../../../../../../../common/services'; + +interface Props { + packageInfo: PackageInfo; + integration?: string | null; +} + +export const DocumentationPage: React.FunctionComponent = ({ packageInfo, integration }) => { + const { docLinks } = useStartServices(); + + const content = ( + <> + + + + + + + ), + }} + /> + + + + + + + + + + + + + ); + + return ( + + + {content} + + ); +}; + +type RegistryInputWithStreams = RegistryInput & { + key: string; + streams: Array; +}; + +const Inputs: React.FunctionComponent<{ + packageInfo: PackageInfo; + integration?: string | null; +}> = ({ packageInfo, integration }) => { + const inputs = useMemo( + () => + packageInfo.policy_templates?.reduce((acc, policyTemplate) => { + if (integration && policyTemplate.name !== integration) { + return acc; + } + if ('inputs' in policyTemplate && policyTemplate.inputs) { + return [ + ...acc, + ...policyTemplate.inputs.map((input) => ({ + key: `${policyTemplate.name}-${input.type}`, + ...input, + streams: getStreamsForInputType(input.type, packageInfo, []), + })), + ]; + } + return acc; + }, [] as RegistryInputWithStreams[]), + [packageInfo, integration] + ); + return ( + <> + + +

+ +

+
+ + {inputs?.map((input) => { + return ( + + {input.key}({input.title}) + + } + initialIsOpen={false} + paddingSize={'m'} + > + {input.description} + {input.vars ? ( + <> + + + + ) : null} + + +
+ +
+
+ + {input.streams.map((dataStream) => ( + + {dataStream.data_stream.dataset}({dataStream.title}) + + } + initialIsOpen={false} + paddingSize={'m'} + > + {dataStream.description} + {dataStream.vars ? ( + <> + + + + ) : null} + + ))} +
+ ); + }) ?? null} + + ); +}; + +const PackageVars: React.FunctionComponent<{ vars: PackageInfo['vars'] }> = ({ vars }) => { + if (!vars) { + return null; + } + + return ( + <> + +

+ +

+
+ + + + + ); +}; + +const VarsTable: React.FunctionComponent<{ vars: RegistryVarsEntry[] }> = ({ vars }) => { + const columns = useMemo((): EuiInMemoryTableProps['columns'] => { + return [ + { + field: 'name', + name: ( + + ), + render: (name: string) => {name}, + }, + { + field: 'title', + name: ( + + ), + }, + { + field: 'type', + name: ( + + ), + }, + { + field: 'required', + width: '70px', + name: ( + + ), + }, + { + field: 'multi', + width: '70px', + name: ( + + ), + }, + ]; + }, []); + + return ( + <> + +
+ +
+
+ + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index dfe1c6001cede..c55adfe4768d1 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -59,6 +59,7 @@ import { OverviewPage } from './overview'; import { PackagePoliciesPage } from './policies'; import { SettingsPage } from './settings'; import { CustomViewPage } from './custom'; +import { DocumentationPage } from './documentation'; import './index.scss'; @@ -489,6 +490,22 @@ export function Detail() { }); } + tabs.push({ + id: 'api-reference', + name: ( + + ), + isSelected: panel === 'api-reference', + 'data-test-subj': `tab-api-reference`, + href: getHref('integration_details_api_reference', { + pkgkey: packageInfoKey, + ...(integration ? { integration } : {}), + }), + }); + return tabs; }, [ packageInfo, @@ -575,6 +592,9 @@ export function Detail() { + + +
)} diff --git a/x-pack/plugins/fleet/public/components/devtools_request_flyout/devtools_request_flyout.tsx b/x-pack/plugins/fleet/public/components/devtools_request_flyout/devtools_request_flyout.tsx new file mode 100644 index 0000000000000..5de1a42fc26ee --- /dev/null +++ b/x-pack/plugins/fleet/public/components/devtools_request_flyout/devtools_request_flyout.tsx @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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, useRef } from 'react'; + +import { EuiBetaBadge, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import type { EuiButtonEmptyProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { ViewApiRequestFlyout } from '@kbn/es-ui-shared-plugin/public'; +import { KibanaContextProvider, toMountPoint } from '@kbn/kibana-react-plugin/public'; + +import { useStartServices } from '../../hooks'; + +interface DevtoolsRequestFlyoutButtonProps { + title?: string; + description?: string; + isDisabled?: boolean; + request: string; + btnProps?: EuiButtonEmptyProps; +} + +export const DevtoolsRequestFlyoutButton: React.FunctionComponent< + DevtoolsRequestFlyoutButtonProps +> = ({ isDisabled, request, title, description, btnProps = {} }) => { + const flyoutRef = useRef>(); + + const services = useStartServices(); + const onClick = useCallback(() => { + const flyout = services.overlays.openFlyout( + toMountPoint( + + flyout.close()} + request={request} + title={title} + description={description} + /> + , + { theme$: services.theme.theme$ } + ) + ); + + flyoutRef.current = flyout; + }, [services, request, title, description]); + + React.useEffect(() => { + return () => { + flyoutRef.current?.close(); + }; + }, []); + + return ( + + + + ); +}; + +export interface ApiRequestFlyoutProps { + title?: string; + description?: string; + isDisabled?: string; + request: string; + closeFlyout: () => void; +} + +export const ApiRequestFlyout: React.FunctionComponent = ({ + closeFlyout, + title = i18n.translate('xpack.fleet.apiRequestFlyout.title', { + defaultMessage: 'Kibana API Request', + }), + request, + description = i18n.translate('xpack.fleet.apiRequestFlyout.description', { + defaultMessage: 'Perform these request against Kibana', + }), +}) => { + const { application, share } = useStartServices(); + + return ( + + {title} + + + +
+ } + description={description} + request={request} + closeFlyout={closeFlyout} + application={application} + urlService={share.url} + /> + ); +}; diff --git a/x-pack/plugins/fleet/public/components/devtools_request_flyout/index.tsx b/x-pack/plugins/fleet/public/components/devtools_request_flyout/index.tsx new file mode 100644 index 0000000000000..33d7aa37b60a5 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/devtools_request_flyout/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 { ApiRequestFlyout, DevtoolsRequestFlyoutButton } from './devtools_request_flyout'; diff --git a/x-pack/plugins/fleet/public/components/index.ts b/x-pack/plugins/fleet/public/components/index.ts index 4e9d9f0f021b0..7eb22e4c459bb 100644 --- a/x-pack/plugins/fleet/public/components/index.ts +++ b/x-pack/plugins/fleet/public/components/index.ts @@ -25,4 +25,5 @@ export * from './link_and_revision'; export * from './agent_enrollment_flyout'; export * from './platform_selector'; export { ConfirmForceInstallModal } from './confirm_force_install_modal'; +export { DevtoolsRequestFlyoutButton } from './devtools_request_flyout'; export { HeaderReleaseBadge, InlineReleaseBadge } from './release_badge'; diff --git a/x-pack/plugins/fleet/public/constants/page_paths.ts b/x-pack/plugins/fleet/public/constants/page_paths.ts index 479110f2f0142..d24496603f32d 100644 --- a/x-pack/plugins/fleet/public/constants/page_paths.ts +++ b/x-pack/plugins/fleet/public/constants/page_paths.ts @@ -30,6 +30,7 @@ export type DynamicPage = | 'integration_details_assets' | 'integration_details_settings' | 'integration_details_custom' + | 'integration_details_api_reference' | 'integration_policy_edit' | 'integration_policy_upgrade' | 'policy_details' @@ -90,6 +91,7 @@ export const INTEGRATIONS_ROUTING_PATHS = { integration_details_assets: '/detail/:pkgkey/assets', integration_details_settings: '/detail/:pkgkey/settings', integration_details_custom: '/detail/:pkgkey/custom', + integration_details_api_reference: '/detail/:pkgkey/api-reference', integration_policy_edit: '/edit-integration/:packagePolicyId', integration_policy_upgrade: '/edit-integration/:packagePolicyId', }; @@ -143,6 +145,10 @@ export const pagePathGetters: { INTEGRATIONS_BASE_PATH, `/detail/${pkgkey}/custom${integration ? `?integration=${integration}` : ''}`, ], + integration_details_api_reference: ({ pkgkey, integration }) => [ + INTEGRATIONS_BASE_PATH, + `/detail/${pkgkey}/api-reference${integration ? `?integration=${integration}` : ''}`, + ], integration_policy_edit: ({ packagePolicyId }) => [ INTEGRATIONS_BASE_PATH, `/edit-integration/${packagePolicyId}`, diff --git a/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx b/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx index 4c8d7dba71761..51ff2cb0f8245 100644 --- a/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx +++ b/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx @@ -24,6 +24,7 @@ import { FleetAppContext } from '../applications/fleet/app'; import { IntegrationsAppContext } from '../applications/integrations/app'; import type { FleetConfigType } from '../plugin'; import type { UIExtensionsStorage } from '../types'; +import { ExperimentalFeaturesService } from '../services'; import { createConfigurationMock } from './plugin_configuration'; import { createStartMock } from './plugin_interfaces'; @@ -63,6 +64,12 @@ export const createFleetTestRendererMock = (): TestRenderer => { const history = createMemoryHistory({ initialEntries: [basePath] }); const mountHistory = new CoreScopedHistory(history, basePath); + ExperimentalFeaturesService.init({ + createPackagePolicyMultiPageLayout: true, + packageVerification: true, + showDevtoolsRequest: false, + }); + const HookWrapper = memo(({ children }) => { return ( diff --git a/x-pack/plugins/fleet/server/errors/handlers.test.ts b/x-pack/plugins/fleet/server/errors/handlers.test.ts index 95850829e9f97..a4e6cfafbf951 100644 --- a/x-pack/plugins/fleet/server/errors/handlers.test.ts +++ b/x-pack/plugins/fleet/server/errors/handlers.test.ts @@ -12,14 +12,14 @@ import { createAppContextStartContractMock } from '../mocks'; import { appContextService } from '../services'; import { - IngestManagerError, + FleetError, RegistryError, PackageNotFoundError, PackageUnsupportedMediaTypeError, - defaultIngestErrorHandler, + defaultFleetErrorHandler, } from '.'; -describe('defaultIngestErrorHandler', () => { +describe('defaultFleetErrorHandler', () => { let mockContract: ReturnType; beforeEach(async () => { // prevents `Logger not set.` and other appContext errors @@ -32,12 +32,12 @@ describe('defaultIngestErrorHandler', () => { appContextService.stop(); }); - describe('IngestManagerError', () => { + describe('FleetError', () => { it('502: RegistryError', async () => { const error = new RegistryError('xyz'); const response = httpServerMock.createResponseFactory(); - await defaultIngestErrorHandler({ error, response }); + await defaultFleetErrorHandler({ error, response }); // response expect(response.ok).toHaveBeenCalledTimes(0); @@ -56,7 +56,7 @@ describe('defaultIngestErrorHandler', () => { const error = new PackageUnsupportedMediaTypeError('123'); const response = httpServerMock.createResponseFactory(); - await defaultIngestErrorHandler({ error, response }); + await defaultFleetErrorHandler({ error, response }); // response expect(response.ok).toHaveBeenCalledTimes(0); @@ -75,7 +75,7 @@ describe('defaultIngestErrorHandler', () => { const error = new PackageNotFoundError('123'); const response = httpServerMock.createResponseFactory(); - await defaultIngestErrorHandler({ error, response }); + await defaultFleetErrorHandler({ error, response }); // response expect(response.ok).toHaveBeenCalledTimes(0); @@ -90,11 +90,11 @@ describe('defaultIngestErrorHandler', () => { expect(mockContract.logger?.error).toHaveBeenCalledWith(error.message); }); - it('400: IngestManagerError', async () => { - const error = new IngestManagerError('123'); + it('400: FleetError', async () => { + const error = new FleetError('123'); const response = httpServerMock.createResponseFactory(); - await defaultIngestErrorHandler({ error, response }); + await defaultFleetErrorHandler({ error, response }); // response expect(response.ok).toHaveBeenCalledTimes(0); @@ -115,7 +115,7 @@ describe('defaultIngestErrorHandler', () => { const error = new Boom.Boom('bam'); const response = httpServerMock.createResponseFactory(); - await defaultIngestErrorHandler({ error, response }); + await defaultFleetErrorHandler({ error, response }); // response expect(response.ok).toHaveBeenCalledTimes(0); @@ -136,7 +136,7 @@ describe('defaultIngestErrorHandler', () => { }); const response = httpServerMock.createResponseFactory(); - await defaultIngestErrorHandler({ error, response }); + await defaultFleetErrorHandler({ error, response }); // response expect(response.ok).toHaveBeenCalledTimes(0); @@ -155,7 +155,7 @@ describe('defaultIngestErrorHandler', () => { const error = Boom.badRequest('nope'); const response = httpServerMock.createResponseFactory(); - await defaultIngestErrorHandler({ error, response }); + await defaultFleetErrorHandler({ error, response }); // response expect(response.ok).toHaveBeenCalledTimes(0); @@ -174,7 +174,7 @@ describe('defaultIngestErrorHandler', () => { const error = Boom.notFound('sorry'); const response = httpServerMock.createResponseFactory(); - await defaultIngestErrorHandler({ error, response }); + await defaultFleetErrorHandler({ error, response }); // response expect(response.ok).toHaveBeenCalledTimes(0); @@ -195,7 +195,7 @@ describe('defaultIngestErrorHandler', () => { const error = new Error('something'); const response = httpServerMock.createResponseFactory(); - await defaultIngestErrorHandler({ error, response }); + await defaultFleetErrorHandler({ error, response }); // response expect(response.ok).toHaveBeenCalledTimes(0); diff --git a/x-pack/plugins/fleet/server/errors/handlers.ts b/x-pack/plugins/fleet/server/errors/handlers.ts index e048136328529..89811ca2bc7aa 100644 --- a/x-pack/plugins/fleet/server/errors/handlers.ts +++ b/x-pack/plugins/fleet/server/errors/handlers.ts @@ -22,7 +22,7 @@ import { AgentActionNotFoundError, AgentPolicyNameExistsError, ConcurrentInstallOperationError, - IngestManagerError, + FleetError, PackageNotFoundError, PackageUnsupportedMediaTypeError, RegistryConnectionError, @@ -37,7 +37,7 @@ type IngestErrorHandler = ( params: IngestErrorHandlerParams ) => IKibanaResponse | Promise; interface IngestErrorHandlerParams { - error: IngestManagerError | Boom.Boom | Error; + error: FleetError | Boom.Boom | Error; response: KibanaResponseFactory; request?: KibanaRequest; context?: RequestHandlerContext; @@ -45,7 +45,7 @@ interface IngestErrorHandlerParams { // unsure if this is correct. would prefer to use something "official" // this type is based on BadRequest values observed while debugging https://github.com/elastic/kibana/issues/75862 -const getHTTPResponseCode = (error: IngestManagerError): number => { +const getHTTPResponseCode = (error: FleetError): number => { if (error instanceof RegistryResponseError) { // 4xx/5xx's from EPR return 500; @@ -81,10 +81,10 @@ const getHTTPResponseCode = (error: IngestManagerError): number => { return 400; // Bad Request }; -export function ingestErrorToResponseOptions(error: IngestErrorHandlerParams['error']) { +export function fleetErrorToResponseOptions(error: IngestErrorHandlerParams['error']) { const logger = appContextService.getLogger(); // our "expected" errors - if (error instanceof IngestManagerError) { + if (error instanceof FleetError) { // only log the message logger.error(error.message); return { @@ -114,10 +114,10 @@ export function ingestErrorToResponseOptions(error: IngestErrorHandlerParams['er }; } -export const defaultIngestErrorHandler: IngestErrorHandler = async ({ +export const defaultFleetErrorHandler: IngestErrorHandler = async ({ error, response, }: IngestErrorHandlerParams): Promise => { - const options = ingestErrorToResponseOptions(error); + const options = fleetErrorToResponseOptions(error); return response.customError(options); }; diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index 713804d8a36f7..1b8a627a35722 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -8,32 +8,28 @@ /* eslint-disable max-classes-per-file */ import type { ElasticsearchErrorDetails } from '@kbn/es-errors'; -import type { FleetErrorType } from '../../common/types'; +import { FleetError } from '../../common/errors'; import { isESClientError } from './utils'; - -export { defaultIngestErrorHandler, ingestErrorToResponseOptions } from './handlers'; +export { + defaultFleetErrorHandler as defaultFleetErrorHandler, + fleetErrorToResponseOptions, +} from './handlers'; export { isESClientError } from './utils'; -export class IngestManagerError extends Error { - attributes?: { type: FleetErrorType }; - constructor(message?: string, public readonly meta?: unknown) { - super(message); - this.name = this.constructor.name; // for stack traces - } -} +export { FleetError as FleetError } from '../../common/errors'; -export class RegistryError extends IngestManagerError {} +export class RegistryError extends FleetError {} export class RegistryConnectionError extends RegistryError {} export class RegistryResponseError extends RegistryError { constructor(message?: string, public readonly status?: number) { super(message); } } -export class PackageNotFoundError extends IngestManagerError {} -export class PackageKeyInvalidError extends IngestManagerError {} -export class PackageOutdatedError extends IngestManagerError {} -export class PackageFailedVerificationError extends IngestManagerError { +export class PackageNotFoundError extends FleetError {} +export class PackageKeyInvalidError extends FleetError {} +export class PackageOutdatedError extends FleetError {} +export class PackageFailedVerificationError extends FleetError { constructor(pkgName: string, pkgVersion: string) { super(`${pkgName}-${pkgVersion} failed signature verification.`); this.attributes = { @@ -41,54 +37,54 @@ export class PackageFailedVerificationError extends IngestManagerError { }; } } -export class AgentPolicyError extends IngestManagerError {} -export class AgentPolicyNotFoundError extends IngestManagerError {} -export class AgentNotFoundError extends IngestManagerError {} -export class AgentActionNotFoundError extends IngestManagerError {} +export class AgentPolicyError extends FleetError {} +export class AgentPolicyNotFoundError extends FleetError {} +export class AgentNotFoundError extends FleetError {} +export class AgentActionNotFoundError extends FleetError {} export class AgentPolicyNameExistsError extends AgentPolicyError {} -export class PackageUnsupportedMediaTypeError extends IngestManagerError {} -export class PackageInvalidArchiveError extends IngestManagerError {} -export class PackageCacheError extends IngestManagerError {} -export class PackageOperationNotSupportedError extends IngestManagerError {} -export class ConcurrentInstallOperationError extends IngestManagerError {} -export class AgentReassignmentError extends IngestManagerError {} -export class PackagePolicyIneligibleForUpgradeError extends IngestManagerError {} -export class PackagePolicyValidationError extends IngestManagerError {} -export class PackagePolicyNotFoundError extends IngestManagerError {} -export class BundledPackageNotFoundError extends IngestManagerError {} -export class HostedAgentPolicyRestrictionRelatedError extends IngestManagerError { +export class PackageUnsupportedMediaTypeError extends FleetError {} +export class PackageInvalidArchiveError extends FleetError {} +export class PackageCacheError extends FleetError {} +export class PackageOperationNotSupportedError extends FleetError {} +export class ConcurrentInstallOperationError extends FleetError {} +export class AgentReassignmentError extends FleetError {} +export class PackagePolicyIneligibleForUpgradeError extends FleetError {} +export class PackagePolicyValidationError extends FleetError {} +export class PackagePolicyNotFoundError extends FleetError {} +export class BundledPackageNotFoundError extends FleetError {} +export class HostedAgentPolicyRestrictionRelatedError extends FleetError { constructor(message = 'Cannot perform that action') { super( `${message} in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.` ); } } -export class PackagePolicyRestrictionRelatedError extends IngestManagerError { +export class PackagePolicyRestrictionRelatedError extends FleetError { constructor(message = 'Cannot perform that action') { super( `${message} in Fleet because the package policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.` ); } } -export class FleetEncryptedSavedObjectEncryptionKeyRequired extends IngestManagerError {} -export class FleetSetupError extends IngestManagerError {} -export class GenerateServiceTokenError extends IngestManagerError {} -export class FleetUnauthorizedError extends IngestManagerError {} +export class FleetEncryptedSavedObjectEncryptionKeyRequired extends FleetError {} +export class FleetSetupError extends FleetError {} +export class GenerateServiceTokenError extends FleetError {} +export class FleetUnauthorizedError extends FleetError {} -export class OutputUnauthorizedError extends IngestManagerError {} -export class OutputInvalidError extends IngestManagerError {} -export class OutputLicenceError extends IngestManagerError {} -export class DownloadSourceError extends IngestManagerError {} +export class OutputUnauthorizedError extends FleetError {} +export class OutputInvalidError extends FleetError {} +export class OutputLicenceError extends FleetError {} +export class DownloadSourceError extends FleetError {} -export class ArtifactsClientError extends IngestManagerError {} -export class ArtifactsClientAccessDeniedError extends IngestManagerError { +export class ArtifactsClientError extends FleetError {} +export class ArtifactsClientAccessDeniedError extends FleetError { constructor(deniedPackageName: string, allowedPackageName: string) { super( `Access denied. Artifact package name (${deniedPackageName}) does not match ${allowedPackageName}` ); } } -export class ArtifactsElasticsearchError extends IngestManagerError { +export class ArtifactsElasticsearchError extends FleetError { readonly requestDetails: string; constructor(public readonly meta: Error) { diff --git a/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts index 36c1fd8401584..9495b8a214dea 100644 --- a/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts @@ -16,7 +16,7 @@ import type { } from '../../types/rest_spec'; import type { ActionsService } from '../../services/agents'; import type { PostNewAgentActionResponse } from '../../../common/types/rest_spec'; -import { defaultIngestErrorHandler } from '../../errors'; +import { defaultFleetErrorHandler } from '../../errors'; export const postNewAgentActionHandlerBuilder = function ( actionsService: ActionsService @@ -45,7 +45,7 @@ export const postNewAgentActionHandlerBuilder = function ( return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; }; @@ -65,7 +65,7 @@ export const postCancelActionHandlerBuilder = function ( return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; }; diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 0f931f85fa3af..1ae6fb81742d1 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -44,7 +44,7 @@ import type { PostBulkUpdateAgentTagsRequestSchema, GetActionStatusRequestSchema, } from '../../types'; -import { defaultIngestErrorHandler } from '../../errors'; +import { defaultFleetErrorHandler } from '../../errors'; import * as AgentService from '../../services/agents'; export const getAgentHandler: RequestHandler< @@ -66,7 +66,7 @@ export const getAgentHandler: RequestHandler< }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -92,7 +92,7 @@ export const deleteAgentHandler: RequestHandler< }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -126,7 +126,7 @@ export const updateAgentHandler: RequestHandler< }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -161,7 +161,7 @@ export const bulkUpdateAgentTagsHandler: RequestHandler< return response.ok({ body: { ...body, actionId: results.actionId } }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -198,7 +198,7 @@ export const getAgentsHandler: RequestHandler< }; return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -220,7 +220,7 @@ export const getAgentTagsHandler: RequestHandler< }; return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -243,7 +243,7 @@ export const putAgentsReassignHandler: RequestHandler< const body: PutAgentReassignResponse = {}; return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -277,7 +277,7 @@ export const postBulkAgentsReassignHandler: RequestHandler< return response.ok({ body: { ...body, actionId: results.actionId } }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -298,7 +298,7 @@ export const getAgentStatusForAgentPolicyHandler: RequestHandler< return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -324,7 +324,7 @@ export const getAgentDataHandler: RequestHandler< return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -361,7 +361,7 @@ export const getAvailableVersionsHandler: RequestHandler = async (context, reque const body: GetAvailableVersionsResponse = { items: versionsToDisplay }; return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -377,6 +377,6 @@ export const getActionStatusHandler: RequestHandler< const body: GetActionStatusResponse = { items: actionStatuses }; return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts b/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts index 8cc4a7b13e687..db5a497a30869 100644 --- a/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts @@ -17,7 +17,7 @@ import type { PostBulkAgentUnenrollRequestSchema, } from '../../types'; import * as AgentService from '../../services/agents'; -import { defaultIngestErrorHandler } from '../../errors'; +import { defaultFleetErrorHandler } from '../../errors'; export const postAgentUnenrollHandler: RequestHandler< TypeOf, @@ -36,7 +36,7 @@ export const postAgentUnenrollHandler: RequestHandler< const body: PostAgentUnenrollResponse = {}; return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -69,6 +69,6 @@ export const postBulkAgentsUnenrollHandler: RequestHandler< return response.ok({ body: { ...body, actionId: results.actionId } }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts index ff78da6e63d1a..4af0c1ff653b2 100644 --- a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts @@ -19,7 +19,7 @@ import type { import type { PostAgentUpgradeRequestSchema, PostBulkAgentUpgradeRequestSchema } from '../../types'; import * as AgentService from '../../services/agents'; import { appContextService } from '../../services'; -import { defaultIngestErrorHandler } from '../../errors'; +import { defaultFleetErrorHandler } from '../../errors'; import { isAgentUpgradeable } from '../../../common/services'; import { getMaxVersion } from '../../../common/services/get_min_max_version'; import { getAgentById } from '../../services/agents'; @@ -78,7 +78,7 @@ export const postAgentUpgradeHandler: RequestHandler< const body: PostAgentUpgradeResponse = {}; return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -135,7 +135,7 @@ export const postBulkAgentsUpgradeHandler: RequestHandler< return response.ok({ body: { ...body, actionId: results.actionId } }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -148,7 +148,7 @@ export const getCurrentUpgradesHandler: RequestHandler = async (context, request const body: GetCurrentUpgradesResponse = { items: upgrades }; return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts index 8f8b3b2af0b19..42f4560f736bc 100644 --- a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts @@ -41,7 +41,7 @@ import type { GetFullAgentManifestResponse, BulkGetAgentPoliciesResponse, } from '../../../common/types'; -import { defaultIngestErrorHandler, AgentPolicyNotFoundError } from '../../errors'; +import { defaultFleetErrorHandler, AgentPolicyNotFoundError } from '../../errors'; import { createAgentPolicyWithPackages } from '../../services/agent_policy_create'; async function populateAssignedAgentsCount( @@ -87,7 +87,7 @@ export const getAgentPoliciesHandler: FleetRequestHandler< return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -122,7 +122,7 @@ export const bulkGetAgentPoliciesHandler: FleetRequestHandler< }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -147,7 +147,7 @@ export const getOneAgentPolicyHandler: RequestHandler< }); } } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -183,7 +183,7 @@ export const createAgentPolicyHandler: FleetRequestHandler< body, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -216,7 +216,7 @@ export const updateAgentPolicyHandler: FleetRequestHandler< body, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -245,7 +245,7 @@ export const copyAgentPolicyHandler: RequestHandler< body, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -267,7 +267,7 @@ export const deleteAgentPoliciesHandler: RequestHandler< body, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -299,7 +299,7 @@ export const getFullAgentPolicy: FleetRequestHandler< }); } } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } } else { try { @@ -322,7 +322,7 @@ export const getFullAgentPolicy: FleetRequestHandler< }); } } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } } }; @@ -361,7 +361,7 @@ export const downloadFullAgentPolicy: FleetRequestHandler< }); } } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } } else { try { @@ -385,7 +385,7 @@ export const downloadFullAgentPolicy: FleetRequestHandler< }); } } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } } }; @@ -412,7 +412,7 @@ export const getK8sManifest: FleetRequestHandler< }); } } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -441,6 +441,6 @@ export const downloadK8sManifest: FleetRequestHandler< }); } } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/fleet/server/routes/app/index.ts b/x-pack/plugins/fleet/server/routes/app/index.ts index 56dd99268b599..dd16c34ede805 100644 --- a/x-pack/plugins/fleet/server/routes/app/index.ts +++ b/x-pack/plugins/fleet/server/routes/app/index.ts @@ -11,7 +11,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { APP_API_ROUTES } from '../../constants'; import { appContextService } from '../../services'; import type { CheckPermissionsResponse, GenerateServiceTokenResponse } from '../../../common/types'; -import { defaultIngestErrorHandler, GenerateServiceTokenError } from '../../errors'; +import { defaultFleetErrorHandler, GenerateServiceTokenError } from '../../errors'; import type { FleetAuthzRouter } from '../security'; import type { FleetRequestHandler } from '../../types'; import { CheckPermissionsRequestSchema } from '../../types'; @@ -77,11 +77,11 @@ export const generateServiceTokenHandler: RequestHandler = async (context, reque }); } else { const error = new GenerateServiceTokenError('Unable to generate service token'); - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } } catch (e) { const error = new GenerateServiceTokenError(e); - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts index 682eb1d796431..d761f8b5e7f30 100644 --- a/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts @@ -11,7 +11,7 @@ import type { DataStream } from '../../types'; import { KibanaSavedObjectType } from '../../../common/types'; import type { GetDataStreamsResponse } from '../../../common/types'; import { getPackageSavedObjects } from '../../services/epm/packages/get'; -import { defaultIngestErrorHandler } from '../../errors'; +import { defaultFleetErrorHandler } from '../../errors'; import { getDataStreamsQueryMetadata } from './get_data_streams_query_metadata'; @@ -198,6 +198,6 @@ export const getListHandler: RequestHandler = async (context, request, response) body, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/fleet/server/routes/download_source/handler.ts b/x-pack/plugins/fleet/server/routes/download_source/handler.ts index f0419a7d1d438..b82c3ad19de55 100644 --- a/x-pack/plugins/fleet/server/routes/download_source/handler.ts +++ b/x-pack/plugins/fleet/server/routes/download_source/handler.ts @@ -21,7 +21,7 @@ import type { GetDownloadSourceResponse, } from '../../../common/types'; import { downloadSourceService } from '../../services/download_source'; -import { defaultIngestErrorHandler } from '../../errors'; +import { defaultFleetErrorHandler } from '../../errors'; import { agentPolicyService } from '../../services'; export const getDownloadSourcesHandler: RequestHandler = async (context, request, response) => { @@ -38,7 +38,7 @@ export const getDownloadSourcesHandler: RequestHandler = async (context, request return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -61,7 +61,7 @@ export const getOneDownloadSourcesHandler: RequestHandler< }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -98,7 +98,7 @@ export const putDownloadSourcesHandler: RequestHandler< }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -123,7 +123,7 @@ export const postDownloadSourcesHandler: RequestHandler< return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -146,6 +146,6 @@ export const deleteDownloadSourcesHandler: RequestHandler< }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts index 942e8e4632ee8..60d8a2de45119 100644 --- a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts +++ b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts @@ -22,7 +22,7 @@ import type { } from '../../../common/types'; import * as APIKeyService from '../../services/api_keys'; import { agentPolicyService } from '../../services/agent_policy'; -import { defaultIngestErrorHandler, AgentPolicyNotFoundError } from '../../errors'; +import { defaultFleetErrorHandler, AgentPolicyNotFoundError } from '../../errors'; export const getEnrollmentApiKeysHandler: RequestHandler< undefined, @@ -47,7 +47,7 @@ export const getEnrollmentApiKeysHandler: RequestHandler< return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; export const postEnrollmentApiKeyHandler: RequestHandler< @@ -78,7 +78,7 @@ export const postEnrollmentApiKeyHandler: RequestHandler< return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -98,7 +98,7 @@ export const deleteEnrollmentApiKeyHandler: RequestHandler< body: { message: `EnrollmentAPIKey ${request.params.keyId} not found` }, }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -119,6 +119,6 @@ export const getOneEnrollmentApiKeyHandler: RequestHandler< }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index bc02068fa9f27..e5e2cc5128074 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -51,11 +51,7 @@ import { getInstallation, } from '../../services/epm/packages'; import type { BulkInstallResponse } from '../../services/epm/packages'; -import { - defaultIngestErrorHandler, - ingestErrorToResponseOptions, - IngestManagerError, -} from '../../errors'; +import { defaultFleetErrorHandler, fleetErrorToResponseOptions, FleetError } from '../../errors'; import { licenseService } from '../../services'; import { getArchiveEntry } from '../../services/epm/archive/cache'; import { getAsset } from '../../services/epm/archive/storage'; @@ -78,7 +74,7 @@ export const getCategoriesHandler: FleetRequestHandler< }; return response.ok({ body, headers: { ...CACHE_CONTROL_10_MINUTES_HEADER } }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -103,7 +99,7 @@ export const getListHandler: FleetRequestHandler< headers: request.query.excludeInstallStatus ? { ...CACHE_CONTROL_10_MINUTES_HEADER } : {}, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -119,7 +115,7 @@ export const getLimitedListHandler: FleetRequestHandler = async (context, reques body, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -193,7 +189,7 @@ export const getFileHandler: FleetRequestHandler< }); } } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -204,7 +200,7 @@ export const getInfoHandler: FleetRequestHandler< const savedObjectsClient = (await context.fleet).epm.internalSoClient; const { pkgName, pkgVersion } = request.params; if (pkgVersion && !semverValid(pkgVersion)) { - throw new IngestManagerError('Package version is not a valid semver'); + throw new FleetError('Package version is not a valid semver'); } const res = await getPackageInfo({ savedObjectsClient, @@ -217,7 +213,7 @@ export const getInfoHandler: FleetRequestHandler< }; return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -237,7 +233,7 @@ export const updatePackageHandler: FleetRequestHandler< return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -252,7 +248,7 @@ export const getStatsHandler: FleetRequestHandler< }; return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -287,7 +283,7 @@ export const installPackageFromRegistryHandler: FleetRequestHandler< }; return response.ok({ body }); } else { - return await defaultIngestErrorHandler({ error: res.error, response }); + return await defaultFleetErrorHandler({ error: res.error, response }); } }; @@ -295,7 +291,7 @@ const bulkInstallServiceResponseToHttpEntry = ( result: BulkInstallResponse ): BulkInstallPackageInfo | IBulkInstallPackageHTTPError => { if (isBulkInstallError(result)) { - const { statusCode, body } = ingestErrorToResponseOptions(result.error); + const { statusCode, body } = fleetErrorToResponseOptions(result.error); return { name: result.name, statusCode, @@ -366,7 +362,7 @@ export const installPackageByUploadHandler: FleetRequestHandler< }; return response.ok({ body }); } else { - return defaultIngestErrorHandler({ error: res.error, response }); + return defaultFleetErrorHandler({ error: res.error, response }); } }; @@ -393,6 +389,6 @@ export const deletePackageHandler: FleetRequestHandler< }; return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/fleet/server/routes/output/handler.ts b/x-pack/plugins/fleet/server/routes/output/handler.ts index 407e8dc09fd2c..9a597be874bd6 100644 --- a/x-pack/plugins/fleet/server/routes/output/handler.ts +++ b/x-pack/plugins/fleet/server/routes/output/handler.ts @@ -21,7 +21,7 @@ import type { PostLogstashApiKeyResponse, } from '../../../common/types'; import { outputService } from '../../services/output'; -import { defaultIngestErrorHandler, FleetUnauthorizedError } from '../../errors'; +import { defaultFleetErrorHandler, FleetUnauthorizedError } from '../../errors'; import { agentPolicyService } from '../../services'; import { generateLogstashApiKey, canCreateLogstashApiKey } from '../../services/api_keys'; @@ -39,7 +39,7 @@ export const getOutputsHandler: RequestHandler = async (context, request, respon return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -62,7 +62,7 @@ export const getOneOuputHandler: RequestHandler< }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -95,7 +95,7 @@ export const putOuputHandler: RequestHandler< }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -120,7 +120,7 @@ export const postOuputHandler: RequestHandler< return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -143,7 +143,7 @@ export const deleteOutputHandler: RequestHandler< }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -164,6 +164,6 @@ export const postLogstashApiKeyHandler: RequestHandler = async (context, request return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; 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 2dd49d505a276..2b4ebbb5728f2 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts @@ -36,12 +36,12 @@ import type { UpgradePackagePolicyResponse, } from '../../../common/types'; import { installationStatuses } from '../../../common/constants'; -import { defaultIngestErrorHandler, PackagePolicyNotFoundError } from '../../errors'; +import { defaultFleetErrorHandler, PackagePolicyNotFoundError } from '../../errors'; import { getInstallations, getPackageInfo } from '../../services/epm/packages'; import { PACKAGES_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../constants'; -import { simplifiedPackagePolicytoNewPackagePolicy } from '../../services/package_policies/simplified_package_policy_helper'; +import { simplifiedPackagePolicytoNewPackagePolicy } from '../../../common/services/simplified_package_policy_helper'; -import type { SimplifiedPackagePolicy } from '../../services/package_policies/simplified_package_policy_helper'; +import type { SimplifiedPackagePolicy } from '../../../common/services/simplified_package_policy_helper'; export const getPackagePoliciesHandler: RequestHandler< undefined, @@ -62,7 +62,7 @@ export const getPackagePoliciesHandler: RequestHandler< }, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -90,7 +90,7 @@ export const bulkGetPackagePoliciesHandler: RequestHandler< }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -117,7 +117,7 @@ export const getOnePackagePolicyHandler: RequestHandler< if (SavedObjectsErrorHelpers.isNotFoundError(error)) { return notFoundResponse(); } else { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } } }; @@ -162,7 +162,7 @@ export const getOrphanedPackagePolicies: RequestHandler = }, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -250,7 +250,7 @@ export const createPackagePolicyHandler: FleetRequestHandler< body: { message: error.message }, }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -340,7 +340,7 @@ export const updatePackagePolicyHandler: RequestHandler< body: { item: updatedPackagePolicy }, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -376,7 +376,7 @@ export const deletePackagePolicyHandler: RequestHandler< body, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -423,7 +423,7 @@ export const deleteOnePackagePolicyHandler: RequestHandler< body: { id: request.params.packagePolicyId }, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -456,7 +456,7 @@ export const upgradePackagePolicyHandler: RequestHandler< body, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -488,6 +488,6 @@ export const dryRunUpgradePackagePolicyHandler: RequestHandler< body, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/fleet/server/routes/preconfiguration/handler.ts b/x-pack/plugins/fleet/server/routes/preconfiguration/handler.ts index 69b4f7f330a0d..64f98746a19f9 100644 --- a/x-pack/plugins/fleet/server/routes/preconfiguration/handler.ts +++ b/x-pack/plugins/fleet/server/routes/preconfiguration/handler.ts @@ -14,7 +14,7 @@ import type { PutPreconfigurationSchema, PostResetOnePreconfiguredAgentPoliciesSchema, } from '../../types'; -import { defaultIngestErrorHandler } from '../../errors'; +import { defaultFleetErrorHandler } from '../../errors'; import { ensurePreconfiguredPackagesAndPolicies, outputService, @@ -48,7 +48,7 @@ export const updatePreconfigurationHandler: FleetRequestHandler< ); return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -65,7 +65,7 @@ export const resetOnePreconfigurationHandler: FleetRequestHandler< await resetPreconfiguredAgentPolicies(soClient, esClient, request.params.agentPolicyId); return response.ok({}); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -82,6 +82,6 @@ export const resetPreconfigurationHandler: FleetRequestHandler< await resetPreconfiguredAgentPolicies(soClient, esClient); return response.ok({}); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/fleet/server/routes/settings/index.ts b/x-pack/plugins/fleet/server/routes/settings/index.ts index 07bd3342ade1d..f11244d7b59ff 100644 --- a/x-pack/plugins/fleet/server/routes/settings/index.ts +++ b/x-pack/plugins/fleet/server/routes/settings/index.ts @@ -10,7 +10,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { SETTINGS_API_ROUTES } from '../../constants'; import type { FleetRequestHandler } from '../../types'; import { PutSettingsRequestSchema, GetSettingsRequestSchema } from '../../types'; -import { defaultIngestErrorHandler } from '../../errors'; +import { defaultFleetErrorHandler } from '../../errors'; import { settingsService, agentPolicyService, appContextService } from '../../services'; import type { FleetAuthzRouter } from '../security'; @@ -30,7 +30,7 @@ export const getSettingsHandler: FleetRequestHandler = async (context, request, }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -59,7 +59,7 @@ export const putSettingsHandler: FleetRequestHandler< }); } - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.ts index bc4cb483036fc..cf2ff46cd1110 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.ts @@ -9,7 +9,7 @@ import { appContextService } from '../../services'; import type { GetFleetStatusResponse, PostFleetSetupResponse } from '../../../common/types'; import { formatNonFatalErrors, setupFleet } from '../../services/setup'; import { hasFleetServers } from '../../services/fleet_server'; -import { defaultIngestErrorHandler } from '../../errors'; +import { defaultFleetErrorHandler } from '../../errors'; import type { FleetRequestHandler } from '../../types'; import { getGpgKeyIdOrUndefined } from '../../services/epm/packages/package_verification'; @@ -54,7 +54,7 @@ export const getFleetStatusHandler: FleetRequestHandler = async (context, reques body, }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; @@ -69,6 +69,6 @@ export const fleetSetupHandler: FleetRequestHandler = async (context, request, r }; return response.ok({ body }); } catch (error) { - return defaultIngestErrorHandler({ error, response }); + return defaultFleetErrorHandler({ error, response }); } }; diff --git a/x-pack/plugins/fleet/server/services/agents/action.mock.ts b/x-pack/plugins/fleet/server/services/agents/action.mock.ts new file mode 100644 index 0000000000000..015914a1016cc --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agents/action.mock.ts @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { elasticsearchServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; + +import type { SavedObject } from '@kbn/core-saved-objects-common'; + +import type { AgentPolicy } from '../../types'; + +export function createClientMock() { + const agentInHostedDoc = { + _id: 'agent-in-hosted-policy', + _source: { + policy_id: 'hosted-agent-policy', + local_metadata: { elastic: { agent: { version: '8.4.0', upgradeable: true } } }, + }, + }; + const agentInHostedDoc2 = { + _id: 'agent-in-hosted-policy2', + _source: { + policy_id: 'hosted-agent-policy', + local_metadata: { elastic: { agent: { version: '8.4.0', upgradeable: true } } }, + }, + }; + const agentInRegularDoc = { + _id: 'agent-in-regular-policy', + _source: { + policy_id: 'regular-agent-policy', + local_metadata: { elastic: { agent: { version: '8.4.0', upgradeable: true } } }, + }, + }; + const agentInRegularDoc2 = { + _id: 'agent-in-regular-policy2', + _source: { + policy_id: 'regular-agent-policy', + local_metadata: { elastic: { agent: { version: '8.4.0', upgradeable: true } } }, + }, + }; + const regularAgentPolicySO = { + id: 'regular-agent-policy', + attributes: { is_managed: false }, + } as SavedObject; + const regularAgentPolicySO2 = { + id: 'regular-agent-policy-2', + attributes: { is_managed: false }, + } as SavedObject; + const hostedAgentPolicySO = { + id: 'hosted-agent-policy', + attributes: { is_managed: true }, + } as SavedObject; + + const soClientMock = savedObjectsClientMock.create(); + + soClientMock.get.mockImplementation(async (_, id) => { + switch (id) { + case regularAgentPolicySO.id: + return regularAgentPolicySO; + case hostedAgentPolicySO.id: + return hostedAgentPolicySO; + case regularAgentPolicySO2.id: + return regularAgentPolicySO2; + default: + throw new Error('not found'); + } + }); + + soClientMock.bulkGet.mockImplementation(async (options) => { + return { + saved_objects: await Promise.all(options!.map(({ type, id }) => soClientMock.get(type, id))), + }; + }); + + const esClientMock = elasticsearchServiceMock.createClusterClient().asInternalUser; + // @ts-expect-error + esClientMock.get.mockResponseImplementation(({ id }) => { + switch (id) { + case agentInHostedDoc._id: + return { body: agentInHostedDoc }; + case agentInHostedDoc2._id: + return { body: agentInHostedDoc2 }; + case agentInRegularDoc2._id: + return { body: agentInRegularDoc2 }; + case agentInRegularDoc._id: + return { body: agentInRegularDoc }; + default: + throw new Error('not found'); + } + }); + esClientMock.bulk.mockResponse( + // @ts-expect-error not full interface + { items: [] } + ); + + esClientMock.mget.mockResponseImplementation((params) => { + // @ts-expect-error + const docs = params?.body.docs.map(({ _id }) => { + let result; + switch (_id) { + case agentInHostedDoc._id: + result = agentInHostedDoc; + break; + case agentInHostedDoc2._id: + result = agentInHostedDoc2; + break; + case agentInRegularDoc2._id: + result = agentInRegularDoc2; + break; + case agentInRegularDoc._id: + result = agentInRegularDoc; + break; + default: + throw new Error('not found'); + } + return { found: true, ...result }; + }); + return { + body: { + docs, + }, + }; + }); + + return { + soClient: soClientMock, + esClient: esClientMock, + agentInHostedDoc, + agentInHostedDoc2, + agentInRegularDoc, + agentInRegularDoc2, + regularAgentPolicySO, + hostedAgentPolicySO, + regularAgentPolicySO2, + }; +} diff --git a/x-pack/plugins/fleet/server/services/agents/action_runner.ts b/x-pack/plugins/fleet/server/services/agents/action_runner.ts index 634bf27ba23ef..d46c88728e341 100644 --- a/x-pack/plugins/fleet/server/services/agents/action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/action_runner.ts @@ -12,12 +12,15 @@ import { withSpan } from '@kbn/apm-utils'; import { isResponseError } from '@kbn/es-errors'; +import moment from 'moment'; + import type { Agent, BulkActionResult } from '../../types'; import { appContextService } from '..'; import { SO_SEARCH_LIMIT } from '../../../common/constants'; import { getAgentActions } from './actions'; import { closePointInTime, getAgentsByKuery } from './crud'; +import type { BulkActionsResolver } from './bulk_actions_resolver'; export interface ActionParams { kuery: string; @@ -43,6 +46,9 @@ export abstract class ActionRunner { protected actionParams: ActionParams; protected retryParams: RetryParams; + private bulkActionsResolver?: BulkActionsResolver; + private checkTaskId?: string; + constructor( esClient: ElasticsearchClient, soClient: SavedObjectsClientContract, @@ -74,46 +80,72 @@ export abstract class ActionRunner { `Running action asynchronously, actionId: ${this.actionParams.actionId}, total agents: ${this.actionParams.total}` ); + if (!this.bulkActionsResolver) { + this.bulkActionsResolver = await appContextService.getBulkActionsResolver(); + } + + // create task to check result with some delay, this runs in case of kibana crash too + this.checkTaskId = await this.createCheckResultTask(); + withSpan({ name: this.getActionType(), type: 'action' }, () => - this.processAgentsInBatches().catch(async (error) => { - // 404 error comes when PIT query is closed - if (isResponseError(error) && error.statusCode === 404) { - const errorMessage = - '404 error from elasticsearch, not retrying. Error: ' + error.message; - appContextService.getLogger().warn(errorMessage); - return; - } - if (this.retryParams.retryCount) { - appContextService - .getLogger() - .error( - `Retry #${this.retryParams.retryCount} of task ${this.retryParams.taskId} failed: ${error.message}` - ); - - if (this.retryParams.retryCount === 3) { - const errorMessage = 'Stopping after 3rd retry. Error: ' + error.message; + this.processAgentsInBatches() + .then(() => { + if (this.checkTaskId) { + // no need for check task, action succeeded + this.bulkActionsResolver!.removeIfExists(this.checkTaskId); + } + }) + .catch(async (error) => { + // 404 error comes when PIT query is closed + if (isResponseError(error) && error.statusCode === 404) { + const errorMessage = + '404 error from elasticsearch, not retrying. Error: ' + error.message; appContextService.getLogger().warn(errorMessage); return; } - } else { - appContextService.getLogger().error(`Action failed: ${error.message}`); - } - const taskId = await appContextService.getBulkActionsResolver()!.run( - this.actionParams, - { - ...this.retryParams, - retryCount: (this.retryParams.retryCount ?? 0) + 1, - }, - this.getTaskType() - ); - - appContextService.getLogger().info(`Retrying in task: ${taskId}`); - }) + if (this.retryParams.retryCount) { + appContextService + .getLogger() + .error( + `Retry #${this.retryParams.retryCount} of task ${this.retryParams.taskId} failed: ${error.message}` + ); + + if (this.retryParams.retryCount === 3) { + const errorMessage = 'Stopping after 3rd retry. Error: ' + error.message; + appContextService.getLogger().warn(errorMessage); + return; + } + } else { + appContextService.getLogger().error(`Action failed: ${error.message}`); + } + const taskId = await this.bulkActionsResolver!.run( + this.actionParams, + { + ...this.retryParams, + retryCount: (this.retryParams.retryCount ?? 0) + 1, + }, + this.getTaskType() + ); + + appContextService.getLogger().info(`Retrying in task: ${taskId}`); + }) ); return { items: [], actionId: this.actionParams.actionId! }; } + private async createCheckResultTask() { + return await this.bulkActionsResolver!.run( + this.actionParams, + { + ...this.retryParams, + retryCount: 1, + }, + this.getTaskType(), + moment(new Date()).add(5, 'm').toDate() + ); + } + private async processBatch(agents: Agent[]): Promise<{ items: BulkActionResult[] }> { if (this.retryParams.retryCount) { try { @@ -176,6 +208,11 @@ export abstract class ActionRunner { const currentResults = await this.processBatch(currentAgents); results = { items: results.items.concat(currentResults.items) }; allAgentsProcessed += currentAgents.length; + if (this.checkTaskId) { + // updating check task with latest checkpoint (this.retryParams.searchAfter) + this.bulkActionsResolver?.removeIfExists(this.checkTaskId); + this.checkTaskId = await this.createCheckResultTask(); + } } await closePointInTime(this.esClient, pitId!); diff --git a/x-pack/plugins/fleet/server/services/agents/actions.ts b/x-pack/plugins/fleet/server/services/agents/actions.ts index a2ba066db7d3f..f215e6dffee7e 100644 --- a/x-pack/plugins/fleet/server/services/agents/actions.ts +++ b/x-pack/plugins/fleet/server/services/agents/actions.ts @@ -14,7 +14,11 @@ import type { NewAgentAction, FleetServerAgentAction, } from '../../../common/types/models'; -import { AGENT_ACTIONS_INDEX, SO_SEARCH_LIMIT } from '../../../common/constants'; +import { + AGENT_ACTIONS_INDEX, + AGENT_ACTIONS_RESULTS_INDEX, + SO_SEARCH_LIMIT, +} from '../../../common/constants'; import { AgentActionNotFoundError } from '../../errors'; import { bulkUpdateAgents } from './crud'; @@ -97,6 +101,42 @@ export async function bulkCreateAgentActions( return actions; } +export async function bulkCreateAgentActionResults( + esClient: ElasticsearchClient, + results: Array<{ + actionId: string; + agentId: string; + error: string; + }> +): Promise { + if (results.length === 0) { + return; + } + + const bulkBody = results.flatMap((result) => { + const body = { + '@timestamp': new Date().toISOString(), + action_id: result.actionId, + agent_id: result.agentId, + error: result.error, + }; + + return [ + { + create: { + _id: uuid.v4(), + }, + }, + body, + ]; + }); + + await esClient.bulk({ + index: AGENT_ACTIONS_RESULTS_INDEX, + body: bulkBody, + }); +} + export async function getAgentActions(esClient: ElasticsearchClient, actionId: string) { const res = await esClient.search({ index: AGENT_ACTIONS_INDEX, diff --git a/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts b/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts index e80db905d48e0..3930f96a9fa44 100644 --- a/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts +++ b/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts @@ -117,9 +117,14 @@ export class BulkActionsResolver { .add(Math.pow(3, retryParams.retryCount ?? 1), 's') .toDate(), }); - appContextService.getLogger().info('Running task ' + taskId); + appContextService.getLogger().info('Scheduling task ' + taskId); return taskId; } + + public async removeIfExists(taskId: string) { + appContextService.getLogger().info('Removing task ' + taskId); + await this.taskManager?.removeIfExists(taskId); + } } export function createRetryTask( diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index 193bc71d04d29..7b3af0c77e626 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -18,7 +18,7 @@ import { SO_SEARCH_LIMIT } from '../../../common/constants'; import { isAgentUpgradeable } from '../../../common/services'; import { AGENTS_PREFIX, AGENTS_INDEX } from '../../constants'; import { escapeSearchQueryPhrase, normalizeKuery } from '../saved_object'; -import { IngestManagerError, isESClientError, AgentNotFoundError } from '../../errors'; +import { FleetError, isESClientError, AgentNotFoundError } from '../../errors'; import { searchHitToAgent, agentSOAttributesToFleetServerAgentDoc } from './helpers'; @@ -53,7 +53,7 @@ function _joinFilters(filters: Array): KueryNode undefined as KueryNode | undefined ); } catch (err) { - throw new IngestManagerError(`Kuery is malformed: ${err.message}`); + throw new FleetError(`Kuery is malformed: ${err.message}`); } } @@ -83,9 +83,7 @@ export async function getAgents(esClient: ElasticsearchClient, options: GetAgent }) ).agents; } else { - throw new IngestManagerError( - 'Either options.agentIds or options.kuery are required to get agents' - ); + throw new FleetError('Either options.agentIds or options.kuery are required to get agents'); } return agents; diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.test.ts b/x-pack/plugins/fleet/server/services/agents/reassign.test.ts index 59267ab6de6db..a54c2cb56c944 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.test.ts @@ -4,44 +4,19 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { elasticsearchServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { SavedObject } from '@kbn/core/server'; -import type { AgentPolicy } from '../../types'; import { HostedAgentPolicyRestrictionRelatedError } from '../../errors'; -import { reassignAgent, reassignAgents } from './reassign'; +import { appContextService } from '../app_context'; +import { createAppContextStartContractMock } from '../../mocks'; -const agentInHostedDoc = { - _id: 'agent-in-hosted-policy', - _source: { policy_id: 'hosted-agent-policy' }, -}; -const agentInHostedDoc2 = { - _id: 'agent-in-hosted-policy2', - _source: { policy_id: 'hosted-agent-policy' }, -}; -const agentInRegularDoc = { - _id: 'agent-in-regular-policy', - _source: { policy_id: 'regular-agent-policy' }, -}; -const regularAgentPolicySO = { - id: 'regular-agent-policy', - attributes: { is_managed: false }, -} as SavedObject; -const regularAgentPolicySO2 = { - id: 'regular-agent-policy-2', - attributes: { is_managed: false }, -} as SavedObject; -const hostedAgentPolicySO = { - id: 'hosted-agent-policy', - attributes: { is_managed: true }, -} as SavedObject; +import { reassignAgent, reassignAgents } from './reassign'; +import { createClientMock } from './action.mock'; describe('reassignAgent (singular)', () => { it('can reassign from regular agent policy to regular', async () => { - const { soClient, esClient } = createClientsMock(); + const { soClient, esClient, agentInRegularDoc, regularAgentPolicySO } = createClientMock(); await reassignAgent(soClient, esClient, agentInRegularDoc._id, regularAgentPolicySO.id); // calls ES update with correct values @@ -55,7 +30,7 @@ describe('reassignAgent (singular)', () => { }); it('cannot reassign from regular agent policy to hosted', async () => { - const { soClient, esClient } = createClientsMock(); + const { soClient, esClient, agentInRegularDoc, hostedAgentPolicySO } = createClientMock(); await expect( reassignAgent(soClient, esClient, agentInRegularDoc._id, hostedAgentPolicySO.id) ).rejects.toThrowError(HostedAgentPolicyRestrictionRelatedError); @@ -65,7 +40,8 @@ describe('reassignAgent (singular)', () => { }); it('cannot reassign from hosted agent policy', async () => { - const { soClient, esClient } = createClientsMock(); + const { soClient, esClient, agentInHostedDoc, hostedAgentPolicySO, regularAgentPolicySO } = + createClientMock(); await expect( reassignAgent(soClient, esClient, agentInHostedDoc._id, regularAgentPolicySO.id) ).rejects.toThrowError(HostedAgentPolicyRestrictionRelatedError); @@ -81,8 +57,22 @@ describe('reassignAgent (singular)', () => { }); describe('reassignAgents (plural)', () => { + beforeEach(async () => { + appContextService.start(createAppContextStartContractMock()); + }); + + afterEach(() => { + appContextService.stop(); + }); it('agents in hosted policies are not updated', async () => { - const { soClient, esClient } = createClientsMock(); + const { + soClient, + esClient, + agentInRegularDoc, + agentInHostedDoc, + agentInHostedDoc2, + regularAgentPolicySO2, + } = createClientMock(); const idsToReassign = [agentInRegularDoc._id, agentInHostedDoc._id, agentInHostedDoc2._id]; await reassignAgents(soClient, esClient, { agentIds: idsToReassign }, regularAgentPolicySO2.id); @@ -92,65 +82,18 @@ describe('reassignAgents (plural)', () => { expect((calledWith as estypes.BulkRequest).body?.length).toBe(2); // @ts-expect-error expect(calledWith.body[0].update._id).toEqual(agentInRegularDoc._id); - }); -}); - -function createClientsMock() { - const soClientMock = savedObjectsClientMock.create(); - // need to mock .create & bulkCreate due to (bulk)createAgentAction(s) in reassignAgent(s) - // @ts-expect-error - soClientMock.create.mockResolvedValue({ attributes: { agent_id: 'test' } }); - soClientMock.bulkCreate.mockImplementation(async ([{ type, attributes }]) => { - return { - saved_objects: [await soClientMock.create(type, attributes)], - }; + // hosted policy is updated in action results with error + const calledWithActionResults = esClient.bulk.mock.calls[1][0] as estypes.BulkRequest; + // bulk write two line per create + expect(calledWithActionResults.body?.length).toBe(4); + const expectedObject = expect.objectContaining({ + '@timestamp': expect.anything(), + action_id: expect.anything(), + agent_id: 'agent-in-hosted-policy', + error: + 'Cannot reassign an agent from hosted agent policy hosted-agent-policy in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', + }); + expect(calledWithActionResults.body?.[1] as any).toEqual(expectedObject); }); - soClientMock.bulkUpdate.mockResolvedValue({ - saved_objects: [], - }); - soClientMock.get.mockImplementation(async (_, id) => { - switch (id) { - case regularAgentPolicySO.id: - return regularAgentPolicySO; - case hostedAgentPolicySO.id: - return hostedAgentPolicySO; - case regularAgentPolicySO2.id: - return regularAgentPolicySO2; - default: - throw new Error(`${id} not found`); - } - }); - soClientMock.bulkGet.mockImplementation(async (options) => { - return { - saved_objects: await Promise.all(options!.map(({ type, id }) => soClientMock.get(type, id))), - }; - }); - - const esClientMock = elasticsearchServiceMock.createClusterClient().asInternalUser; - // @ts-expect-error - esClientMock.mget.mockResponseImplementation(() => { - return { - body: { - docs: [agentInHostedDoc, agentInRegularDoc, agentInHostedDoc2], - }, - }; - }); - // @ts-expect-error - esClientMock.get.mockResponseImplementation(({ id }) => { - switch (id) { - case agentInHostedDoc._id: - return { body: agentInHostedDoc }; - case agentInRegularDoc._id: - return { body: agentInRegularDoc }; - default: - throw new Error(`${id} not found`); - } - }); - esClientMock.bulk.mockResponse( - // @ts-expect-error not full interface - { items: [] } - ); - - return { soClient: soClientMock, esClient: esClientMock }; -} +}); diff --git a/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts index 806d77ff12630..8724a68d330e7 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import uuid from 'uuid'; import type { SavedObjectsClientContract, ElasticsearchClient } from '@kbn/core/server'; import type { Agent, BulkActionResult } from '../../types'; @@ -16,7 +16,7 @@ import { appContextService } from '../app_context'; import { ActionRunner } from './action_runner'; import { errorsToResults, bulkUpdateAgents } from './crud'; -import { createAgentAction } from './actions'; +import { bulkCreateAgentActionResults, createAgentAction } from './actions'; import { getHostedPolicies, isHostedAgent } from './hosted_agent'; import { BulkActionTaskType } from './bulk_actions_resolver'; @@ -62,7 +62,7 @@ export async function reassignBatch( const agentsToUpdate = givenAgents.reduce((agents, agent) => { if (agent.policy_id === options.newAgentPolicyId) { errors[agent.id] = new AgentReassignmentError( - `${agent.id} is already assigned to ${options.newAgentPolicyId}` + `Agent ${agent.id} is already assigned to agent policy ${options.newAgentPolicyId}` ); } else if (isHostedAgent(hostedPolicies, agent)) { errors[agent.id] = new HostedAgentPolicyRestrictionRelatedError( @@ -95,17 +95,39 @@ export async function reassignBatch( })) ); + const actionId = options.actionId ?? uuid(); + const errorCount = Object.keys(errors).length; + const total = options.total ?? agentsToUpdate.length + errorCount; + const now = new Date().toISOString(); await createAgentAction(esClient, { - id: options.actionId, + id: actionId, agents: agentsToUpdate.map((agent) => agent.id), created_at: now, type: 'POLICY_REASSIGN', - total: options.total, + total, data: { policy_id: options.newAgentPolicyId, }, }); + if (errorCount > 0) { + appContextService + .getLogger() + .info( + `Skipping ${errorCount} agents, as failed validation (already assigned or assigned to hosted policy)` + ); + + // writing out error result for those agents that failed validation, so the action is not going to stay in progress forever + await bulkCreateAgentActionResults( + esClient, + Object.keys(errors).map((agentId) => ({ + agentId, + actionId, + error: errors[agentId].message, + })) + ); + } + return result; } diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts index 668ab79da691f..f3bbb2036954f 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts @@ -5,44 +5,25 @@ * 2.0. */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { elasticsearchServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; -import type { SavedObject } from '@kbn/core/server'; -import type { AgentPolicy } from '../../types'; import { HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { invalidateAPIKeys } from '../api_keys'; +import { appContextService } from '../app_context'; + +import { createAppContextStartContractMock } from '../../mocks'; + import { unenrollAgent, unenrollAgents } from './unenroll'; import { invalidateAPIKeysForAgents } from './unenroll_action_runner'; +import { createClientMock } from './action.mock'; jest.mock('../api_keys'); const mockedInvalidateAPIKeys = invalidateAPIKeys as jest.MockedFunction; -const agentInHostedDoc = { - _id: 'agent-in-hosted-policy', - _source: { policy_id: 'hosted-agent-policy' }, -}; -const agentInRegularDoc = { - _id: 'agent-in-regular-policy', - _source: { policy_id: 'regular-agent-policy' }, -}; -const agentInRegularDoc2 = { - _id: 'agent-in-regular-policy2', - _source: { policy_id: 'regular-agent-policy' }, -}; -const regularAgentPolicySO = { - id: 'regular-agent-policy', - attributes: { is_managed: false }, -} as SavedObject; -const hostedAgentPolicySO = { - id: 'hosted-agent-policy', - attributes: { is_managed: true }, -} as SavedObject; - describe('unenrollAgent (singular)', () => { it('can unenroll from regular agent policy', async () => { - const { soClient, esClient } = createClientMock(); + const { soClient, esClient, agentInRegularDoc } = createClientMock(); await unenrollAgent(soClient, esClient, agentInRegularDoc._id); // calls ES update with correct values @@ -55,7 +36,7 @@ describe('unenrollAgent (singular)', () => { }); it('cannot unenroll from hosted agent policy by default', async () => { - const { soClient, esClient } = createClientMock(); + const { soClient, esClient, agentInHostedDoc } = createClientMock(); await expect(unenrollAgent(soClient, esClient, agentInHostedDoc._id)).rejects.toThrowError( HostedAgentPolicyRestrictionRelatedError ); @@ -64,7 +45,7 @@ describe('unenrollAgent (singular)', () => { }); it('cannot unenroll from hosted agent policy with revoke=true', async () => { - const { soClient, esClient } = createClientMock(); + const { soClient, esClient, agentInHostedDoc } = createClientMock(); await expect( unenrollAgent(soClient, esClient, agentInHostedDoc._id, { revoke: true }) ).rejects.toThrowError(HostedAgentPolicyRestrictionRelatedError); @@ -73,7 +54,7 @@ describe('unenrollAgent (singular)', () => { }); it('can unenroll from hosted agent policy with force=true', async () => { - const { soClient, esClient } = createClientMock(); + const { soClient, esClient, agentInHostedDoc } = createClientMock(); await unenrollAgent(soClient, esClient, agentInHostedDoc._id, { force: true }); // calls ES update with correct values expect(esClient.update).toBeCalledTimes(1); @@ -85,7 +66,7 @@ describe('unenrollAgent (singular)', () => { }); it('can unenroll from hosted agent policy with force=true and revoke=true', async () => { - const { soClient, esClient } = createClientMock(); + const { soClient, esClient, agentInHostedDoc } = createClientMock(); await unenrollAgent(soClient, esClient, agentInHostedDoc._id, { force: true, revoke: true }); // calls ES update with correct values expect(esClient.update).toBeCalledTimes(1); @@ -96,8 +77,15 @@ describe('unenrollAgent (singular)', () => { }); describe('unenrollAgents (plural)', () => { + beforeEach(async () => { + appContextService.start(createAppContextStartContractMock()); + }); + + afterEach(() => { + appContextService.stop(); + }); it('can unenroll from an regular agent policy', async () => { - const { soClient, esClient } = createClientMock(); + const { soClient, esClient, agentInRegularDoc, agentInRegularDoc2 } = createClientMock(); const idsToUnenroll = [agentInRegularDoc._id, agentInRegularDoc2._id]; await unenrollAgents(soClient, esClient, { agentIds: idsToUnenroll }); @@ -115,14 +103,15 @@ describe('unenrollAgents (plural)', () => { } }); it('cannot unenroll from a hosted agent policy by default', async () => { - const { soClient, esClient } = createClientMock(); + const { soClient, esClient, agentInHostedDoc, agentInRegularDoc, agentInRegularDoc2 } = + createClientMock(); const idsToUnenroll = [agentInRegularDoc._id, agentInHostedDoc._id, agentInRegularDoc2._id]; await unenrollAgents(soClient, esClient, { agentIds: idsToUnenroll }); // calls ES update with correct values const onlyRegular = [agentInRegularDoc._id, agentInRegularDoc2._id]; - const calledWith = esClient.bulk.mock.calls[0][0]; + const calledWith = esClient.bulk.mock.calls[1][0]; const ids = (calledWith as estypes.BulkRequest)?.body ?.filter((i: any) => i.update !== undefined) .map((i: any) => i.update._id); @@ -133,10 +122,24 @@ describe('unenrollAgents (plural)', () => { for (const doc of docs!) { expect(doc).toHaveProperty('unenrollment_started_at'); } + + // hosted policy is updated in action results with error + const calledWithActionResults = esClient.bulk.mock.calls[0][0] as estypes.BulkRequest; + // bulk write two line per create + expect(calledWithActionResults.body?.length).toBe(2); + const expectedObject = expect.objectContaining({ + '@timestamp': expect.anything(), + action_id: expect.anything(), + agent_id: 'agent-in-hosted-policy', + error: + 'Cannot unenroll agent-in-hosted-policy from a hosted agent policy hosted-agent-policy in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', + }); + expect(calledWithActionResults.body?.[1] as any).toEqual(expectedObject); }); it('cannot unenroll from a hosted agent policy with revoke=true', async () => { - const { soClient, esClient } = createClientMock(); + const { soClient, esClient, agentInHostedDoc, agentInRegularDoc, agentInRegularDoc2 } = + createClientMock(); const idsToUnenroll = [agentInRegularDoc._id, agentInHostedDoc._id, agentInRegularDoc2._id]; @@ -176,7 +179,8 @@ describe('unenrollAgents (plural)', () => { }); it('can unenroll from hosted agent policy with force=true', async () => { - const { soClient, esClient } = createClientMock(); + const { soClient, esClient, agentInHostedDoc, agentInRegularDoc, agentInRegularDoc2 } = + createClientMock(); const idsToUnenroll = [agentInRegularDoc._id, agentInHostedDoc._id, agentInRegularDoc2._id]; await unenrollAgents(soClient, esClient, { agentIds: idsToUnenroll, force: true }); @@ -195,7 +199,8 @@ describe('unenrollAgents (plural)', () => { }); it('can unenroll from hosted agent policy with force=true and revoke=true', async () => { - const { soClient, esClient } = createClientMock(); + const { soClient, esClient, agentInHostedDoc, agentInRegularDoc, agentInRegularDoc2 } = + createClientMock(); const idsToUnenroll = [agentInRegularDoc._id, agentInHostedDoc._id, agentInRegularDoc2._id]; @@ -265,66 +270,3 @@ describe('invalidateAPIKeysForAgents', () => { ]); }); }); - -function createClientMock() { - const soClientMock = savedObjectsClientMock.create(); - - soClientMock.get.mockImplementation(async (_, id) => { - switch (id) { - case regularAgentPolicySO.id: - return regularAgentPolicySO; - case hostedAgentPolicySO.id: - return hostedAgentPolicySO; - default: - throw new Error('not found'); - } - }); - - soClientMock.bulkGet.mockImplementation(async (options) => { - return { - saved_objects: await Promise.all(options!.map(({ type, id }) => soClientMock.get(type, id))), - }; - }); - - const esClientMock = elasticsearchServiceMock.createClusterClient().asInternalUser; - // @ts-expect-error - esClientMock.get.mockResponseImplementation(({ id }) => { - switch (id) { - case agentInHostedDoc._id: - return { body: agentInHostedDoc }; - case agentInRegularDoc2._id: - return { body: agentInRegularDoc2 }; - case agentInRegularDoc._id: - return { body: agentInRegularDoc }; - default: - throw new Error('not found'); - } - }); - esClientMock.bulk.mockResponse( - // @ts-expect-error not full interface - { items: [] } - ); - - esClientMock.mget.mockResponseImplementation((params) => { - // @ts-expect-error - const docs = params?.body.docs.map(({ _id }) => { - switch (_id) { - case agentInHostedDoc._id: - return agentInHostedDoc; - case agentInRegularDoc2._id: - return agentInRegularDoc2; - case agentInRegularDoc._id: - return agentInRegularDoc; - default: - throw new Error('not found'); - } - }); - return { - body: { - docs, - }, - }; - }); - - return { soClient: soClientMock, esClient: esClientMock }; -} diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts index 6eda4b00499e1..70cfcece227ba 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import uuid from 'uuid'; import type { SavedObjectsClientContract, ElasticsearchClient } from '@kbn/core/server'; import type { Agent, BulkActionResult } from '../../types'; @@ -13,10 +13,12 @@ import { HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { invalidateAPIKeys } from '../api_keys'; +import { appContextService } from '../app_context'; + import { ActionRunner } from './action_runner'; import { errorsToResults, bulkUpdateAgents } from './crud'; -import { createAgentAction } from './actions'; +import { bulkCreateAgentActionResults, createAgentAction } from './actions'; import { getHostedPolicies, isHostedAgent } from './hosted_agent'; import { BulkActionTaskType } from './bulk_actions_resolver'; @@ -72,6 +74,10 @@ export async function unenrollBatch( return agents; }, []); + const actionId = options.actionId ?? uuid(); + const errorCount = Object.keys(outgoingErrors).length; + const total = options.total ?? agentsToUpdate.length + errorCount; + const now = new Date().toISOString(); if (options.revoke) { // Get all API keys that need to be invalidated @@ -79,12 +85,30 @@ export async function unenrollBatch( } else { // Create unenroll action for each agent await createAgentAction(esClient, { - id: options.actionId, + id: actionId, agents: agentsToUpdate.map((agent) => agent.id), created_at: now, type: 'UNENROLL', - total: options.total, + total, }); + + if (errorCount > 0) { + appContextService + .getLogger() + .info( + `Skipping ${errorCount} agents, as failed validation (cannot unenroll from a hosted policy)` + ); + + // writing out error result for those agents that failed validation, so the action is not going to stay in progress forever + await bulkCreateAgentActionResults( + esClient, + Object.keys(outgoingErrors).map((agentId) => ({ + agentId, + actionId, + error: outgoingErrors[agentId].message, + })) + ); + } } // Update the necessary agents diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.test.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.test.ts new file mode 100644 index 0000000000000..9692f05822879 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.test.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import { appContextService } from '../app_context'; + +import { createAppContextStartContractMock } from '../../mocks'; + +import { sendUpgradeAgentsActions } from './upgrade'; +import { createClientMock } from './action.mock'; + +describe('sendUpgradeAgentsActions (plural)', () => { + beforeEach(async () => { + appContextService.start(createAppContextStartContractMock()); + }); + + afterEach(() => { + appContextService.stop(); + }); + it('can upgrade from an regular agent policy', async () => { + const { soClient, esClient, agentInRegularDoc, agentInRegularDoc2 } = createClientMock(); + const idsToAction = [agentInRegularDoc._id, agentInRegularDoc2._id]; + await sendUpgradeAgentsActions(soClient, esClient, { agentIds: idsToAction, version: '8.5.0' }); + + // calls ES update with correct values + const calledWith = esClient.bulk.mock.calls[0][0]; + const ids = (calledWith as estypes.BulkRequest)?.body + ?.filter((i: any) => i.update !== undefined) + .map((i: any) => i.update._id); + const docs = (calledWith as estypes.BulkRequest)?.body + ?.filter((i: any) => i.doc) + .map((i: any) => i.doc); + expect(ids).toEqual(idsToAction); + for (const doc of docs!) { + expect(doc).toHaveProperty('upgrade_started_at'); + expect(doc.upgrade_status).toEqual('started'); + } + }); + it('cannot upgrade from a hosted agent policy by default', async () => { + const { soClient, esClient, agentInHostedDoc, agentInRegularDoc, agentInRegularDoc2 } = + createClientMock(); + + const idsToAction = [agentInRegularDoc._id, agentInHostedDoc._id, agentInRegularDoc2._id]; + await sendUpgradeAgentsActions(soClient, esClient, { agentIds: idsToAction, version: '8.5.0' }); + + // calls ES update with correct values + const onlyRegular = [agentInRegularDoc._id, agentInRegularDoc2._id]; + const calledWith = esClient.bulk.mock.calls[1][0]; + const ids = (calledWith as estypes.BulkRequest)?.body + ?.filter((i: any) => i.update !== undefined) + .map((i: any) => i.update._id); + const docs = (calledWith as estypes.BulkRequest)?.body + ?.filter((i: any) => i.doc) + .map((i: any) => i.doc); + expect(ids).toEqual(onlyRegular); + for (const doc of docs!) { + expect(doc).toHaveProperty('upgrade_started_at'); + expect(doc.upgrade_status).toEqual('started'); + } + + // hosted policy is updated in action results with error + const calledWithActionResults = esClient.bulk.mock.calls[0][0] as estypes.BulkRequest; + // bulk write two line per create + expect(calledWithActionResults.body?.length).toBe(2); + const expectedObject = expect.objectContaining({ + '@timestamp': expect.anything(), + action_id: expect.anything(), + agent_id: 'agent-in-hosted-policy', + error: + 'Cannot upgrade agent in hosted agent policy hosted-agent-policy in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', + }); + expect(calledWithActionResults.body?.[1] as any).toEqual(expectedObject); + }); + + it('can upgrade from hosted agent policy with force=true', async () => { + const { soClient, esClient, agentInHostedDoc, agentInRegularDoc, agentInRegularDoc2 } = + createClientMock(); + const idsToAction = [agentInRegularDoc._id, agentInHostedDoc._id, agentInRegularDoc2._id]; + await sendUpgradeAgentsActions(soClient, esClient, { + agentIds: idsToAction, + force: true, + version: '8.5.0', + }); + + // calls ES update with correct values + const calledWith = esClient.bulk.mock.calls[0][0]; + const ids = (calledWith as estypes.BulkRequest)?.body + ?.filter((i: any) => i.update !== undefined) + .map((i: any) => i.update._id); + const docs = (calledWith as estypes.BulkRequest)?.body + ?.filter((i: any) => i.doc) + .map((i: any) => i.doc); + expect(ids).toEqual(idsToAction); + for (const doc of docs!) { + expect(doc).toHaveProperty('upgrade_started_at'); + expect(doc.upgrade_status).toEqual('started'); + } + }); +}); diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts index ca2bd6a996d67..28c1ec3d26007 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts @@ -8,12 +8,13 @@ import type { SavedObjectsClientContract, ElasticsearchClient } from '@kbn/core/server'; import moment from 'moment'; +import uuid from 'uuid'; import { isAgentUpgradeable } from '../../../common/services'; import type { Agent, BulkActionResult } from '../../types'; -import { HostedAgentPolicyRestrictionRelatedError, IngestManagerError } from '../../errors'; +import { HostedAgentPolicyRestrictionRelatedError, FleetError } from '../../errors'; import { appContextService } from '../app_context'; @@ -21,7 +22,7 @@ import { ActionRunner } from './action_runner'; import type { GetAgentsOptions } from './crud'; import { errorsToResults, bulkUpdateAgents } from './crud'; -import { createAgentAction } from './actions'; +import { bulkCreateAgentActionResults, createAgentAction } from './actions'; import { getHostedPolicies, isHostedAgent } from './hosted_agent'; import { BulkActionTaskType } from './bulk_actions_resolver'; @@ -80,7 +81,7 @@ export async function upgradeBatch( const isNotAllowed = !options.force && !isAgentUpgradeable(agent, kibanaVersion, options.version); if (isNotAllowed) { - throw new IngestManagerError(`${agent.id} is not upgradeable`); + throw new FleetError(`Agent ${agent.id} is not upgradeable`); } if (!options.force && isHostedAgent(hostedPolicies, agent)) { @@ -115,17 +116,39 @@ export async function upgradeBatch( options.upgradeDurationSeconds ); + const actionId = options.actionId ?? uuid(); + const errorCount = Object.keys(errors).length; + const total = options.total ?? agentsToUpdate.length + errorCount; + await createAgentAction(esClient, { - id: options.actionId, + id: actionId, created_at: now, data, ack_data: data, type: 'UPGRADE', - total: options.total, + total, agents: agentsToUpdate.map((agent) => agent.id), ...rollingUpgradeOptions, }); + if (errorCount > 0) { + appContextService + .getLogger() + .info( + `Skipping ${errorCount} agents, as failed validation (cannot upgrade hosted agent or agent not upgradeable)` + ); + + // writing out error result for those agents that failed validation, so the action is not going to stay in progress forever + await bulkCreateAgentActionResults( + esClient, + Object.keys(errors).map((agentId) => ({ + agentId, + actionId, + error: errors[agentId].message, + })) + ); + } + await bulkUpdateAgents( esClient, agentsToUpdate.map((agent) => ({ diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts index 0f49cbe6c9fe2..0221ad2662a64 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts @@ -16,7 +16,7 @@ import { toElasticsearchQuery, fromKueryExpression } from '@kbn/es-query'; import type { ESSearchResponse as SearchResponse } from '@kbn/core/types/elasticsearch'; import type { EnrollmentAPIKey, FleetServerEnrollmentAPIKey } from '../../types'; -import { IngestManagerError } from '../../errors'; +import { FleetError } from '../../errors'; import { ENROLLMENT_API_KEYS_INDEX } from '../../constants'; import { agentPolicyService } from '../agent_policy'; import { escapeSearchQueryPhrase } from '../saved_object'; @@ -193,7 +193,7 @@ export async function generateEnrollmentAPIKey( k.name?.replace(providedKeyName, '').trim().match(uuidRegex) ) ) { - throw new IngestManagerError( + throw new FleetError( i18n.translate('xpack.fleet.serverError.enrollmentKeyDuplicate', { defaultMessage: 'An enrollment key named {providedKeyName} already exists for agent policy {agentPolicyId}', diff --git a/x-pack/plugins/fleet/server/services/download_source.ts b/x-pack/plugins/fleet/server/services/download_source.ts index e95e7eee014f9..690585a9fcd53 100644 --- a/x-pack/plugins/fleet/server/services/download_source.ts +++ b/x-pack/plugins/fleet/server/services/download_source.ts @@ -13,7 +13,7 @@ import { } from '../constants'; import type { DownloadSource, DownloadSourceAttributes, DownloadSourceBase } from '../types'; -import { DownloadSourceError, IngestManagerError } from '../errors'; +import { DownloadSourceError, FleetError } from '../errors'; import { SO_SEARCH_LIMIT } from '../../common'; import { agentPolicyService } from './agent_policy'; @@ -199,7 +199,7 @@ class DownloadSourceService { ? `Download Source '${idsWithName[0]}' already exists` : `Download Sources '${idsWithName.join(',')}' already exist`; - throw new IngestManagerError(`${existClause} with name '${downloadSource.name}'`); + throw new FleetError(`${existClause} with name '${downloadSource.name}'`); } } } diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts index 17d0d2b914670..02e39d89b14f7 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts @@ -9,7 +9,7 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/ import { appContextService } from '../../..'; import { ElasticsearchAssetType } from '../../../../types'; -import { IngestManagerError } from '../../../../errors'; +import { FleetError } from '../../../../errors'; import type { EsAssetReference } from '../../../../../common/types'; import { updateEsAssetReferences } from '../../packages/install'; @@ -51,7 +51,7 @@ export async function deletePipeline(esClient: ElasticsearchClient, id: string): // Only throw if error is not a 404 error. Sometimes the pipeline is already deleted, but we have // duplicate references to them, see https://github.com/elastic/kibana/issues/91192 if (err.statusCode !== 404) { - throw new IngestManagerError(`error deleting pipeline ${id}: ${err}`); + throw new FleetError(`error deleting pipeline ${id}: ${err}`); } } } diff --git a/x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts b/x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts index 6934134c34aca..e18b64d8daae8 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/bundled_packages.ts @@ -9,7 +9,7 @@ import fs from 'fs/promises'; import path from 'path'; import type { BundledPackage } from '../../../types'; -import { IngestManagerError } from '../../../errors'; +import { FleetError } from '../../../errors'; import { appContextService } from '../../app_context'; import { splitPkgKey } from '../registry'; @@ -19,7 +19,7 @@ export async function getBundledPackages(): Promise { const bundledPackageLocation = config?.developer?.bundledPackageLocation; if (!bundledPackageLocation) { - throw new IngestManagerError('xpack.fleet.developer.bundledPackageLocation is not configured'); + throw new FleetError('xpack.fleet.developer.bundledPackageLocation is not configured'); } try { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index 30a87f1f35a54..e69c5fe3944f0 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -23,11 +23,7 @@ import type { GetCategoriesRequest, } from '../../../../common/types'; import type { Installation, PackageInfo } from '../../../types'; -import { - IngestManagerError, - PackageFailedVerificationError, - PackageNotFoundError, -} from '../../../errors'; +import { FleetError, PackageFailedVerificationError, PackageNotFoundError } from '../../../errors'; import { appContextService } from '../..'; import * as Registry from '../registry'; import { getEsPackage } from '../archive/storage'; @@ -297,7 +293,7 @@ export async function getPackageFromSource(options: { logger.debug(`retrieved uninstalled package ${pkgName}-${pkgVersion} from registry`); } if (!res) { - throw new IngestManagerError(`package info for ${pkgName}-${pkgVersion} does not exist`); + throw new FleetError(`package info for ${pkgName}-${pkgVersion} does not exist`); } return { paths: res.paths, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index 114396eafa04a..91e2c7d241fd7 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -36,7 +36,7 @@ import type { PackageVerificationResult, } from '../../../types'; import { AUTO_UPGRADE_POLICIES_PACKAGES } from '../../../../common/constants'; -import { IngestManagerError, PackageOutdatedError } from '../../../errors'; +import { FleetError, PackageOutdatedError } from '../../../errors'; import { PACKAGES_SAVED_OBJECT_TYPE, MAX_TIME_COMPLETE_INSTALL } from '../../../constants'; import { licenseService } from '../..'; import { appContextService } from '../../app_context'; @@ -170,14 +170,14 @@ export async function handleInstallPackageFailure({ spaceId, }: { savedObjectsClient: SavedObjectsClientContract; - error: IngestManagerError | Boom.Boom | Error; + error: FleetError | Boom.Boom | Error; pkgName: string; pkgVersion: string; installedPkg: SavedObject | undefined; esClient: ElasticsearchClient; spaceId: string; }) { - if (error instanceof IngestManagerError) { + if (error instanceof FleetError) { return; } const logger = appContextService.getLogger(); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/update.ts b/x-pack/plugins/fleet/server/services/epm/packages/update.ts index 224c9332fad62..aa75ab3eae1dd 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/update.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/update.ts @@ -12,7 +12,7 @@ import type { ExperimentalIndexingFeature } from '../../../../common/types'; import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; import type { Installation, UpdatePackageRequestSchema } from '../../../types'; -import { IngestManagerError } from '../../../errors'; +import { FleetError } from '../../../errors'; import { getInstallationObject, getPackageInfo } from './get'; @@ -27,7 +27,7 @@ export async function updatePackage( const installedPackage = await getInstallationObject({ savedObjectsClient, pkgName }); if (!installedPackage) { - throw new IngestManagerError(`package ${pkgName} is not installed`); + throw new FleetError(`package ${pkgName} is not installed`); } await savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, installedPackage.id, { 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 59e4104c62478..b2b0fd20104cc 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -45,7 +45,7 @@ import type { } from '../../common/types'; import { packageToPackagePolicy } from '../../common/services'; -import { IngestManagerError, PackagePolicyIneligibleForUpgradeError } from '../errors'; +import { FleetError, PackagePolicyIneligibleForUpgradeError } from '../errors'; import { preconfigurePackageInputs, @@ -1188,12 +1188,12 @@ describe('Package policy service', () => { }); await expect( packagePolicyService.runDeleteExternalCallbacks(deletedPackagePolicies) - ).rejects.toThrow(IngestManagerError); + ).rejects.toThrow(FleetError); expect(callingOrder).toEqual(['one', 'two']); }); it('should provide an array of errors encountered by running external callbacks', async () => { - let error: IngestManagerError; + let error: FleetError; const callbackOneError = new Error('foo 1'); const callbackTwoError = new Error('foo 2'); @@ -3395,7 +3395,7 @@ describe('Package policy service', () => { expect( packagePolicyService.getUpgradePackagePolicyInfo(savedObjectsClient, 'package-policy-id') - ).rejects.toEqual(new IngestManagerError('Package notinstalled is not installed')); + ).rejects.toEqual(new FleetError('Package notinstalled is not installed')); }); }); }); diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index f45610c0a52c7..0dd16f51dac5f 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -61,8 +61,8 @@ import type { } from '../../common/types'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../constants'; import { - IngestManagerError, - ingestErrorToResponseOptions, + FleetError, + fleetErrorToResponseOptions, PackagePolicyIneligibleForUpgradeError, PackagePolicyValidationError, PackagePolicyRestrictionRelatedError, @@ -137,7 +137,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { if (agentPolicy && packagePolicy.package?.name === FLEET_APM_PACKAGE) { const dataOutput = await getDataOutputForAgentPolicy(soClient, agentPolicy); if (dataOutput.type === outputType.Logstash) { - throw new IngestManagerError('You cannot add APM to a policy using a logstash output'); + throw new FleetError('You cannot add APM to a policy using a logstash output'); } } await validateIsNotHostedPolicy(soClient, packagePolicy.policy_id, options?.force); @@ -152,7 +152,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { // Check that the name does not exist already if (existingPoliciesWithName.items.length > 0) { - throw new IngestManagerError( + throw new FleetError( `An integration policy with the name ${packagePolicy.name} already exists. Please rename it or choose a different name.` ); } @@ -193,7 +193,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { // already contain a package policy for this package if (isPackageLimited(pkgInfo)) { if (agentPolicy && doesAgentPolicyAlreadyIncludePackage(agentPolicy, pkgInfo.name)) { - throw new IngestManagerError( + throw new FleetError( `Unable to create integration policy. Integration '${pkgInfo.name}' already exists on this agent policy.` ); } @@ -492,7 +492,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { const filtered = (existingPoliciesWithName?.items || []).filter((p) => p.id !== id); if (filtered.length > 0) { - throw new IngestManagerError( + throw new FleetError( `An integration policy with the name ${packagePolicy.name} already exists. Please rename it or choose a different name.` ); } @@ -641,7 +641,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { result.push({ id, success: false, - ...ingestErrorToResponseOptions(error), + ...fleetErrorToResponseOptions(error), }); } }; @@ -692,7 +692,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { }); if (!installedPackage) { - throw new IngestManagerError( + throw new FleetError( i18n.translate('xpack.fleet.packagePolicy.packageNotInstalledError', { defaultMessage: 'Package {name} is not installed', values: { @@ -731,7 +731,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { packagePolicy?: PackagePolicy ) { if (!packagePolicy) { - throw new IngestManagerError( + throw new FleetError( i18n.translate('xpack.fleet.packagePolicy.policyNotFoundError', { defaultMessage: 'Package policy with id {id} not found', values: { id }, @@ -740,7 +740,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } if (!packagePolicy.package?.name) { - throw new IngestManagerError( + throw new FleetError( i18n.translate('xpack.fleet.packagePolicy.packageNotFoundError', { defaultMessage: 'Package policy with id {id} has no named package', values: { id }, @@ -804,7 +804,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { result.push({ id, success: false, - ...ingestErrorToResponseOptions(error), + ...fleetErrorToResponseOptions(error), }); } } @@ -887,7 +887,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } catch (error) { return { hasErrors: true, - ...ingestErrorToResponseOptions(error), + ...fleetErrorToResponseOptions(error), }; } } @@ -1139,7 +1139,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } if (errorsThrown.length > 0) { - throw new IngestManagerError( + throw new FleetError( `${errorsThrown.length} encountered while executing package delete external callbacks`, errorsThrown ); diff --git a/x-pack/plugins/graph/public/application.tsx b/x-pack/plugins/graph/public/application.tsx index bc0ee0b3ee55b..d3ba129dbb316 100644 --- a/x-pack/plugins/graph/public/application.tsx +++ b/x-pack/plugins/graph/public/application.tsx @@ -25,12 +25,14 @@ import { DataPlugin, DataViewsContract } from '@kbn/data-plugin/public'; import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { FormattedRelative } from '@kbn/i18n-react'; +import { TableListViewKibanaProvider } from '@kbn/content-management-table-list'; import './index.scss'; import('./font_awesome'); import { SavedObjectsStart } from '@kbn/saved-objects-plugin/public'; import { SpacesApi } from '@kbn/spaces-plugin/public'; -import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaThemeProvider, toMountPoint } from '@kbn/kibana-react-plugin/public'; import { GraphSavePolicy } from './types'; import { graphRouter } from './router'; import { checkLicense } from '../common/check_license'; @@ -110,7 +112,19 @@ export const renderApp = ({ history, element, ...deps }: GraphDependencies) => { window.dispatchEvent(new HashChangeEvent('hashchange')); }); - const app = {graphRouter(deps)}; + const app = ( + + + {graphRouter(deps)} + + + ); ReactDOM.render(app, element); element.setAttribute('class', 'gphAppWrapper'); diff --git a/x-pack/plugins/graph/public/apps/listing_route.tsx b/x-pack/plugins/graph/public/apps/listing_route.tsx index cf9a5ea88d2a8..af869f7afaa21 100644 --- a/x-pack/plugins/graph/public/apps/listing_route.tsx +++ b/x-pack/plugins/graph/public/apps/listing_route.tsx @@ -11,7 +11,8 @@ import { FormattedMessage, I18nProvider } from '@kbn/i18n-react'; import { EuiEmptyPrompt, EuiLink, EuiButton } from '@elastic/eui'; import { ApplicationStart } from '@kbn/core/public'; import { useHistory, useLocation } from 'react-router-dom'; -import { TableListView } from '@kbn/kibana-react-plugin/public'; +import { TableListView } from '@kbn/content-management-table-list'; +import type { UserContentCommonSchema } from '@kbn/content-management-table-list'; import { deleteSavedWorkspace, findSavedWorkspace } from '../helpers/saved_workspace_utils'; import { getEditPath, getEditUrl, getNewPath, setBreadcrumbs } from '../services/url'; import { GraphWorkspaceSavedObject } from '../types'; @@ -20,6 +21,27 @@ import { GraphServices } from '../application'; const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit'; const SAVED_OBJECTS_PER_PAGE_SETTING = 'savedObjects:perPage'; +interface GraphUserContent extends UserContentCommonSchema { + type: string; + attributes: { + title: string; + description?: string; + }; +} + +const toTableListViewSavedObject = (savedObject: GraphWorkspaceSavedObject): GraphUserContent => { + return { + id: savedObject.id!, + updatedAt: savedObject.updatedAt!, + references: savedObject.references, + type: savedObject.type, + attributes: { + title: savedObject.title, + description: savedObject.description, + }, + }; +}; + export interface ListingRouteProps { deps: Omit; } @@ -47,25 +69,23 @@ export function ListingRoute({ { savedObjectsClient, basePath: coreStart.http.basePath }, search, listingLimit - ); + ).then(({ total, hits }) => ({ + total, + hits: hits.map(toTableListViewSavedObject), + })); }, [coreStart.http.basePath, listingLimit, savedObjectsClient] ); const editItem = useCallback( - (savedWorkspace: GraphWorkspaceSavedObject) => { + (savedWorkspace: { id: string }) => { history.push(getEditPath(savedWorkspace)); }, [history] ); - const getViewUrl = useCallback( - (savedWorkspace: GraphWorkspaceSavedObject) => getEditUrl(addBasePath, savedWorkspace), - [addBasePath] - ); - const deleteItems = useCallback( - async (savedWorkspaces: GraphWorkspaceSavedObject[]) => { + async (savedWorkspaces: Array<{ id: string }>) => { await deleteSavedWorkspace( savedObjectsClient, savedWorkspaces.map((cur) => cur.id!) @@ -76,17 +96,13 @@ export function ListingRoute({ return ( - + id="graph" headingId="graphListingHeading" - rowHeader="title" createItem={capabilities.graph.save ? createItem : undefined} findItems={findItems} deleteItems={capabilities.graph.delete ? deleteItems : undefined} editItem={capabilities.graph.save ? editItem : undefined} - tableColumns={getTableColumns(getViewUrl)} listingLimit={listingLimit} initialFilter={initialFilter} initialPageSize={initialPageSize} @@ -95,7 +111,6 @@ export function ListingRoute({ createItem, coreStart.application )} - toastNotifications={coreStart.notifications.toasts} entityName={i18n.translate('xpack.graph.listing.table.entityName', { defaultMessage: 'graph', })} @@ -105,8 +120,7 @@ export function ListingRoute({ tableListTitle={i18n.translate('xpack.graph.listing.graphsTitle', { defaultMessage: 'Graphs', })} - theme={coreStart.theme} - application={coreStart.application} + getDetailViewLink={({ id }) => getEditUrl(addBasePath, { id })} /> ); @@ -188,40 +202,3 @@ function getNoItemsMessage( /> ); } - -// TODO this is an EUI type but EUI doesn't provide this typing yet -interface DataColumn { - field: string; - name: string; - sortable?: boolean; - render?: (value: string, item: GraphWorkspaceSavedObject) => React.ReactNode; - dataType?: 'auto' | 'string' | 'number' | 'date' | 'boolean'; -} - -function getTableColumns(getViewUrl: (record: GraphWorkspaceSavedObject) => string): DataColumn[] { - return [ - { - field: 'title', - name: i18n.translate('xpack.graph.listing.table.titleColumnName', { - defaultMessage: 'Title', - }), - sortable: true, - render: (field, record) => ( - - {field} - - ), - }, - { - field: 'description', - name: i18n.translate('xpack.graph.listing.table.descriptionColumnName', { - defaultMessage: 'Description', - }), - dataType: 'string', - sortable: true, - }, - ]; -} diff --git a/x-pack/plugins/graph/public/types/persistence.ts b/x-pack/plugins/graph/public/types/persistence.ts index 640348d96f6ac..f5c1366ff8661 100644 --- a/x-pack/plugins/graph/public/types/persistence.ts +++ b/x-pack/plugins/graph/public/types/persistence.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SavedObjectReference } from '@kbn/core/public'; import { AdvancedSettings, UrlTemplate, WorkspaceField } from './app_state'; import { WorkspaceNode, WorkspaceEdge } from './workspace_state'; @@ -32,6 +33,8 @@ export interface GraphWorkspaceSavedObject { // Only set for legacy saved objects. legacyIndexPatternRef?: string; _source: Record; + updatedAt?: string; + references: SavedObjectReference[]; } export interface SerializedWorkspaceState { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index 09caa5e20364a..208bec0a1d6e6 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -238,7 +238,7 @@ describe('Data Streams tab', () => { const { table, actions } = testBed; await actions.clickIndicesAt(0); expect(table.getMetaData('indexTable').tableCellsValues).toEqual([ - ['', '', '', '', '', '', '', 'dataStream1'], + ['', 'data-stream-index', '', '', '', '', '', '', 'dataStream1'], ]); }); @@ -374,7 +374,7 @@ describe('Data Streams tab', () => { const { table, actions } = testBed; await actions.clickIndicesAt(0); expect(table.getMetaData('indexTable').tableCellsValues).toEqual([ - ['', '', '', '', '', '', '', '%dataStream'], + ['', 'data-stream-index', '', '', '', '', '', '', '%dataStream'], ]); }); }); diff --git a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx b/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx index 8bc4e5f16cf81..5a6639a1f2298 100644 --- a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import testSubject from '@kbn/test-subj-selector'; +import { subj as testSubject } from '@kbn/test-subj-selector'; import React from 'react'; import { act } from 'react-dom/test-utils'; diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 560fd8a3f79d9..62568a3cbca5e 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -211,6 +211,13 @@ export async function mountApp( ? historyLocationState.payload : undefined; + if (historyLocationState && historyLocationState.type === ACTION_VISUALIZE_LENS_FIELD) { + // remove originatingApp from context when visualizing a field in Lens + // so Lens does not try to return to the original app on Save + // see https://github.com/elastic/kibana/issues/128695 + delete initialContext?.originatingApp; + } + if (embeddableEditorIncomingState?.searchSessionId) { data.search.session.continue(embeddableEditorIncomingState.searchSessionId); } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx index 5c1b5c4d9d8a6..65c714c1731b8 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx @@ -166,7 +166,7 @@ describe('ConfigPanel', () => { .first() .instance(); act(() => { - instance.find('[data-test-subj="lnsLayerRemove"]').first().simulate('click'); + instance.find('[data-test-subj="lnsLayerRemove--0"]').first().simulate('click'); }); instance.update(); act(() => { @@ -193,7 +193,7 @@ describe('ConfigPanel', () => { .first() .instance(); act(() => { - instance.find('[data-test-subj="lnsLayerRemove"]').at(0).simulate('click'); + instance.find('[data-test-subj="lnsLayerRemove--0"]').first().simulate('click'); }); instance.update(); act(() => { @@ -219,7 +219,7 @@ describe('ConfigPanel', () => { .first() .instance(); act(() => { - instance.find('[data-test-subj="lnsLayerRemove"]').at(2).simulate('click'); + instance.find('[data-test-subj="lnsLayerRemove--1"]').first().simulate('click'); }); instance.update(); act(() => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index cb45519250a81..3b0f46e5e6157 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -137,7 +137,7 @@ describe('LayerPanel', () => { it('should show the reset button when single layer', async () => { const { instance } = await mountWithProvider(); expect( - instance.find('[data-test-subj="lnsLayerRemove"]').first().props()['aria-label'] + instance.find('[data-test-subj="lnsLayerRemove--0"]').first().props()['aria-label'] ).toContain('Reset layer'); }); @@ -146,7 +146,7 @@ describe('LayerPanel', () => { ); expect( - instance.find('[data-test-subj="lnsLayerRemove"]').first().props()['aria-label'] + instance.find('[data-test-subj="lnsLayerRemove--0"]').first().props()['aria-label'] ).toContain('Delete layer'); }); @@ -155,7 +155,7 @@ describe('LayerPanel', () => { delete layerPanelAttributes.activeVisualization.removeLayer; const { instance } = await mountWithProvider(); expect( - instance.find('[data-test-subj="lnsLayerRemove"]').first().props()['aria-label'] + instance.find('[data-test-subj="lnsLayerRemove--0"]').first().props()['aria-label'] ).toContain('Reset visualization'); }); @@ -165,7 +165,7 @@ describe('LayerPanel', () => { ); act(() => { - instance.find('[data-test-subj="lnsLayerRemove"]').first().simulate('click'); + instance.find('[data-test-subj="lnsLayerRemove--0"]').first().simulate('click'); }); instance.update(); act(() => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/remove_layer_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/remove_layer_button.tsx index 64c4d808f255a..651ad3f2526aa 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/remove_layer_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/remove_layer_button.tsx @@ -158,7 +158,7 @@ export function RemoveLayerButton({ size="xs" iconType={isOnlyLayer ? 'eraser' : 'trash'} color="danger" - data-test-subj="lnsLayerRemove" + data-test-subj={`lnsLayerRemove--${layerIndex}`} aria-label={ariaLabel} title={ariaLabel} onClick={() => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index 00ea2ae4a1f15..460b981846e48 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -34,6 +34,10 @@ import { RangeIndexPatternColumn, FiltersIndexPatternColumn, PercentileIndexPatternColumn, + CountIndexPatternColumn, + SumIndexPatternColumn, + AvgIndexPatternColumn, + MedianIndexPatternColumn, } from './operations'; import { createMockedFullReference } from './operations/mocks'; import { cloneDeep } from 'lodash'; @@ -589,6 +593,234 @@ describe('IndexPattern Data Source', () => { expect((ast.chain[1].arguments.aggs[1] as Ast).chain[0].arguments.timeShift).toEqual(['1d']); }); + it('should pass time shift and filter parameter to all children metric agg functions but respect local values, too', async () => { + /* + structure of this formula: + moving_average( + count() + + sum(products.price, shift='1h') + + differences( + average(products.price, kql='NOT category : * ') + + median(products.price), + kql='category : *'), + shift='3h') + + * Outer moving average is shifted - this is inherited to the count and the average and median within the nested differences + * The sum has its own shift and does not respected the shift from the moving average + * The differences has a filter which is inherited to the median, but not the average as it has its own filter + */ + const queryBaseState: IndexPatternPrivateState = { + currentIndexPatternId: '1', + layers: { + first: { + indexPatternId: '1', + columns: { + col1: { + label: 'order_date', + dataType: 'date', + operationType: 'date_histogram', + sourceField: 'order_date', + isBucketed: true, + scale: 'interval', + params: { + interval: 'auto', + includeEmptyRows: true, + dropPartials: false, + }, + } as DateHistogramIndexPatternColumn, + col2X0: { + label: + "Part of moving_average(count() + sum(products.price, shift='1h') + differences(average(products.price, kql='NOT category : * ') + median(products.price), kql='category : *'), shift='3h')", + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + params: { + emptyAsNull: false, + }, + customLabel: true, + } as CountIndexPatternColumn, + col2X1: { + label: + "Part of moving_average(count() + sum(products.price, shift='1h') + differences(average(products.price, kql='NOT category : * ') + median(products.price), kql='category : *'), shift='3h')", + dataType: 'number', + operationType: 'sum', + sourceField: 'products.price', + isBucketed: false, + scale: 'ratio', + timeShift: '1h', + params: { + emptyAsNull: false, + }, + customLabel: true, + } as SumIndexPatternColumn, + col2X2: { + label: + "Part of moving_average(count() + sum(products.price, shift='1h') + differences(average(products.price, kql='NOT category : * ') + median(products.price), kql='category : *'), shift='3h')", + dataType: 'number', + operationType: 'average', + sourceField: 'products.price', + isBucketed: false, + scale: 'ratio', + filter: { + query: 'NOT category : * ', + language: 'kuery', + }, + params: { + emptyAsNull: false, + }, + customLabel: true, + } as AvgIndexPatternColumn, + col2X3: { + label: + "Part of moving_average(count() + sum(products.price, shift='1h') + differences(average(products.price, kql='NOT category : * ') + median(products.price), kql='category : *'), shift='3h')", + dataType: 'number', + operationType: 'median', + sourceField: 'products.price', + isBucketed: false, + scale: 'ratio', + params: { + emptyAsNull: false, + }, + customLabel: true, + } as MedianIndexPatternColumn, + col2X4: { + label: + "Part of moving_average(count() + sum(products.price, shift='1h') + differences(average(products.price, kql='NOT category : * ') + median(products.price), kql='category : *'), shift='3h')", + dataType: 'number', + operationType: 'math', + isBucketed: false, + scale: 'ratio', + params: { + tinymathAst: { + type: 'function', + name: 'add', + args: ['col2X2', 'col2X3'] as unknown as TinymathAST[], + location: { + min: 71, + max: 144, + }, + text: "average(products.price, kql='NOT category : * ') + median(products.price)", + }, + }, + references: ['col2X2', 'col2X3'], + customLabel: true, + } as MathIndexPatternColumn, + col2X5: { + label: + "Part of moving_average(count() + sum(products.price, shift='1h') + differences(average(products.price, kql='NOT category : * ') + median(products.price), kql='category : *'), shift='3h')", + dataType: 'number', + operationType: 'differences', + isBucketed: false, + scale: 'ratio', + references: ['col2X4'], + filter: { + query: 'category : *', + language: 'kuery', + }, + customLabel: true, + }, + col2X6: { + label: + "Part of moving_average(count() + sum(products.price, shift='1h') + differences(average(products.price, kql='NOT category : * ') + median(products.price), kql='category : *'), shift='3h')", + dataType: 'number', + operationType: 'math', + isBucketed: false, + scale: 'ratio', + params: { + tinymathAst: { + type: 'function', + name: 'add', + args: [ + { + type: 'function', + name: 'add', + args: ['col2X0', 'col2X1'] as unknown as TinymathAST[], + }, + 'col2X5', + ], + location: { + min: 15, + max: 165, + }, + text: "count() + sum(products.price, shift='1h') + differences(average(products.price, kql='NOT category : * ') + median(products.price), kql='category : *')", + }, + }, + references: ['col2X0', 'col2X1', 'col2X5'], + customLabel: true, + } as MathIndexPatternColumn, + col2X7: { + label: + "Part of moving_average(count() + sum(products.price, shift='1h') + differences(average(products.price, kql='NOT category : * ') + median(products.price), kql='category : *'), shift='3h')", + dataType: 'number', + operationType: 'moving_average', + isBucketed: false, + scale: 'ratio', + references: ['col2X6'], + timeShift: '3h', + params: { + window: 5, + }, + customLabel: true, + } as MovingAverageIndexPatternColumn, + col2: { + label: + "moving_average(count() + sum(products.price, shift='1h') + differences(average(products.price, kql='NOT category : * ') + median(products.price), kql='category : *'), shift='3h')", + dataType: 'number', + operationType: 'formula', + isBucketed: false, + scale: 'ratio', + params: { + formula: + "moving_average(count() + sum(products.price, shift='1h') + differences(average(products.price, kql='NOT category : * ') + median(products.price), kql='category : *'), shift='3h')", + isFormulaBroken: false, + }, + references: ['col2X7'], + } as FormulaIndexPatternColumn, + }, + columnOrder: [ + 'col1', + 'col2', + 'col2X0', + 'col2X1', + 'col2X2', + 'col2X3', + 'col2X4', + 'col2X5', + 'col2X6', + 'col2X7', + ], + incompleteColumns: {}, + }, + }, + }; + + const ast = indexPatternDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns + ) as Ast; + const count = (ast.chain[1].arguments.aggs[1] as Ast).chain[0]; + const sum = (ast.chain[1].arguments.aggs[2] as Ast).chain[0]; + const average = (ast.chain[1].arguments.aggs[3] as Ast).chain[0]; + const median = (ast.chain[1].arguments.aggs[4] as Ast).chain[0]; + expect(count.arguments.timeShift).toEqual(['3h']); + expect(count.arguments.customBucket).toEqual(undefined); + expect(sum.arguments.timeShift).toEqual(['1h']); + expect(sum.arguments.customBucket).toEqual(undefined); + expect(average.arguments.timeShift).toEqual(['3h']); + expect( + ((average.arguments.customBucket[0] as Ast).chain[0].arguments.filter[0] as Ast).chain[0] + .arguments.q[0] + ).toEqual('NOT category : * '); + expect(median.arguments.timeShift).toEqual(['3h']); + expect( + ((median.arguments.customBucket[0] as Ast).chain[0].arguments.filter[0] as Ast).chain[0] + .arguments.q[0] + ).toEqual('category : *'); + }); + it('should wrap filtered metrics in filtered metric aggregation', async () => { const queryBaseState: IndexPatternPrivateState = { currentIndexPatternId: '1', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts b/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts index 81b8e5154a349..7cd3639547e14 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts @@ -9,19 +9,7 @@ import { DragContextState } from '../drag_drop'; import { getFieldByNameFactory } from './pure_helpers'; import type { IndexPattern, IndexPatternField } from '../types'; -export const createMockedIndexPatternWithoutType = ( - typeToFilter: IndexPatternField['type'] -): IndexPattern => { - const { fields, ...otherIndexPatternProps } = createMockedIndexPattern(); - const filteredFields = fields.filter(({ type }) => type !== typeToFilter); - return { - ...otherIndexPatternProps, - fields: filteredFields, - getFieldByName: getFieldByNameFactory(filteredFields), - }; -}; - -export const createMockedIndexPattern = (): IndexPattern => { +export const createMockedIndexPattern = (someProps?: Partial): IndexPattern => { const fields = [ { name: 'timestamp', @@ -103,6 +91,7 @@ export const createMockedIndexPattern = (): IndexPattern => { getFieldByName: getFieldByNameFactory(fields), isPersisted: true, spec: {}, + ...someProps, }; }; @@ -193,6 +182,18 @@ export const createMockedRestrictedIndexPattern = () => { }; }; +export const createMockedIndexPatternWithoutType = ( + typeToFilter: IndexPatternField['type'] +): IndexPattern => { + const { fields, ...otherIndexPatternProps } = createMockedIndexPattern(); + const filteredFields = fields.filter(({ type }) => type !== typeToFilter); + return { + ...otherIndexPatternProps, + fields: filteredFields, + getFieldByName: getFieldByNameFactory(filteredFields), + }; +}; + export function createMockedDragDropContext(): jest.Mocked { return { dragging: undefined, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.test.ts index 085d6e80bf5b6..703156418b364 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.test.ts @@ -22,6 +22,7 @@ import { ReferenceBasedIndexPatternColumn } from '../column_types'; import type { PercentileRanksIndexPatternColumn } from '../percentile_ranks'; import type { PercentileIndexPatternColumn } from '../percentile'; import { MULTI_KEY_VISUAL_SEPARATOR } from './constants'; +import { MovingAverageIndexPatternColumn } from '../calculations'; jest.mock('@kbn/unified-field-list-plugin/public/services/field_stats', () => ({ loadFieldStats: jest.fn().mockResolvedValue({ @@ -151,6 +152,32 @@ describe('getDisallowedTermsMessage()', () => { ).toBeUndefined(); }); + it('should return no error for a single dimension shifted which is wrapped in a referencing column', () => { + expect( + getDisallowedTermsMessage( + getLayer(getStringBasedOperationColumn(), [ + // count will inherit the shift from the moving average + getCountOperationColumn({ timeShift: undefined }), + { + label: 'Moving average', + dataType: 'number', + operationType: 'moving_average', + isBucketed: false, + scale: 'ratio', + references: ['col2'], + timeShift: '3h', + params: { + window: 5, + }, + customLabel: true, + } as MovingAverageIndexPatternColumn, + ]), + 'col1', + indexPattern + ) + ).toBeUndefined(); + }); + it('should return no for multiple fields with no shifted dimensions', () => { expect(getDisallowedTermsMessage(getLayer(), 'col1', indexPattern)).toBeUndefined(); expect( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.ts index b3378558cfd18..7139a8effd4ca 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/helpers.ts @@ -92,11 +92,23 @@ export function getDisallowedTermsMessage( columnId: string, indexPattern: IndexPattern ) { + const referenced: Set = new Set(); + Object.entries(layer.columns).forEach(([cId, c]) => { + if ('references' in c) { + c.references.forEach((r) => { + referenced.add(r); + }); + } + }); const hasMultipleShifts = uniq( - Object.values(layer.columns) - .filter((col) => operationDefinitionMap[col.operationType].shiftable) - .map((col) => col.timeShift || '') + Object.entries(layer.columns) + .filter( + ([colId, col]) => + operationDefinitionMap[col.operationType].shiftable && + (!isReferenced(layer, colId) || col.timeShift) + ) + .map(([colId, col]) => col.timeShift || '') ).length > 1; if (!hasMultipleShifts) { return undefined; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts index 067bbaae43be7..2df8300eb6481 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts @@ -1385,6 +1385,7 @@ describe('state_helpers', () => { it('should execute adjustments for other columns when creating a reference', () => { const termsColumn: TermsIndexPatternColumn = { label: 'Top values of source', + customLabel: true, dataType: 'string', isBucketed: true, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index 30e3b89f8b6d3..2d8b41ce866b4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -975,12 +975,15 @@ function applyReferenceTransition({ }, }, }; - layer = adjustColumnReferencesForChangedColumn( - { - ...newLayer, - columnOrder: getColumnOrder(newLayer), - }, - newId + layer = updateDefaultLabels( + adjustColumnReferencesForChangedColumn( + { + ...newLayer, + columnOrder: getColumnOrder(newLayer), + }, + newId + ), + indexPattern ); return newId; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts index 2e2d61f0672d7..ef0c770de72e2 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts @@ -58,39 +58,54 @@ function getExpressionForLayer( return null; } const columns = { ...layer.columns }; - Object.keys(columns).forEach((columnId) => { + // make sure the columns are in topological order + const sortedColumns = sortedReferences( + columnOrder.map((colId) => [colId, columns[colId]] as const) + ); + + sortedColumns.forEach((columnId) => { const column = columns[columnId]; const rootDef = operationDefinitionMap[column.operationType]; - if ( - 'references' in column && - rootDef.filterable && - rootDef.input === 'fullReference' && - column.filter - ) { + if ('references' in column && rootDef.filterable && column.filter) { // inherit filter to all referenced operations - column.references.forEach((referenceColumnId) => { - const referencedColumn = columns[referenceColumnId]; - const referenceDef = operationDefinitionMap[column.operationType]; - if (referenceDef.filterable) { - columns[referenceColumnId] = { ...referencedColumn, filter: column.filter }; - } - }); + function setFilterForAllReferences(currentColumn: GenericIndexPatternColumn) { + if (!('references' in currentColumn)) return; + currentColumn.references.forEach((referenceColumnId) => { + let referencedColumn = columns[referenceColumnId]; + const hasFilter = referencedColumn.filter; + const referenceDef = operationDefinitionMap[column.operationType]; + if (referenceDef.filterable && !hasFilter) { + referencedColumn = { ...referencedColumn, filter: column.filter }; + columns[referenceColumnId] = referencedColumn; + } + if (!hasFilter) { + // only push through the current filter if the current level doesn't have its own + setFilterForAllReferences(referencedColumn); + } + }); + } + setFilterForAllReferences(column); } - if ( - 'references' in column && - rootDef.shiftable && - rootDef.input === 'fullReference' && - column.timeShift - ) { + if ('references' in column && rootDef.shiftable && column.timeShift) { // inherit time shift to all referenced operations - column.references.forEach((referenceColumnId) => { - const referencedColumn = columns[referenceColumnId]; - const referenceDef = operationDefinitionMap[column.operationType]; - if (referenceDef.shiftable) { - columns[referenceColumnId] = { ...referencedColumn, timeShift: column.timeShift }; - } - }); + function setTimeShiftForAllReferences(currentColumn: GenericIndexPatternColumn) { + if (!('references' in currentColumn)) return; + currentColumn.references.forEach((referenceColumnId) => { + let referencedColumn = columns[referenceColumnId]; + const hasShift = referencedColumn.timeShift; + const referenceDef = operationDefinitionMap[column.operationType]; + if (referenceDef.shiftable && !hasShift) { + referencedColumn = { ...referencedColumn, timeShift: column.timeShift }; + columns[referenceColumnId] = referencedColumn; + } + if (!hasShift) { + // only push through the current time shift if the current level doesn't have its own + setTimeShiftForAllReferences(referencedColumn); + } + }); + } + setTimeShiftForAllReferences(column); } }); diff --git a/x-pack/plugins/lens/public/shared_components/filter_query_input.tsx b/x-pack/plugins/lens/public/shared_components/filter_query_input.tsx index db585f5f28204..9a2ec76d4c799 100644 --- a/x-pack/plugins/lens/public/shared_components/filter_query_input.tsx +++ b/x-pack/plugins/lens/public/shared_components/filter_query_input.tsx @@ -37,6 +37,7 @@ export function FilterQueryInput({ helpMessage, label = filterByLabel, initiallyOpen, + ['data-test-subj']: dataTestSubj, }: { inputFilter: Query | undefined; onChange: (query: Query) => void; @@ -44,6 +45,7 @@ export function FilterQueryInput({ helpMessage?: string | null; label?: string; initiallyOpen?: boolean; + ['data-test-subj']?: string; }) { const [filterPopoverOpen, setFilterPopoverOpen] = useState(Boolean(initiallyOpen)); const { inputValue: queryInput, handleInputChange: setQueryInput } = useDebouncedValue({ @@ -133,6 +135,7 @@ export function FilterQueryInput({ onChange={setQueryInput} isInvalid={!isQueryInputValid} onSubmit={() => {}} + data-test-subj={dataTestSubj} /> diff --git a/x-pack/plugins/lens/public/visualizations/xy/index.ts b/x-pack/plugins/lens/public/visualizations/xy/index.ts index 9dcd1dab63593..26001e7245fef 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/index.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/index.ts @@ -30,7 +30,7 @@ export class XyVisualization { ) { editorFrame.registerVisualization(async () => { const { getXyVisualization } = await import('../../async_services'); - const [coreStart, { charts, data, fieldFormats, eventAnnotation }] = + const [coreStart, { charts, data, fieldFormats, eventAnnotation, unifiedSearch }] = await core.getStartServices(); const [palettes, eventAnnotationService] = await Promise.all([ charts.palettes.getPalettes(), @@ -46,6 +46,7 @@ export class XyVisualization { fieldFormats, useLegacyTimeAxis, kibanaTheme: core.theme, + unifiedSearch, }); }); } diff --git a/x-pack/plugins/lens/public/visualizations/xy/state_helpers.ts b/x-pack/plugins/lens/public/visualizations/xy/state_helpers.ts index 37c08fc7e8668..d683888a88cfd 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/state_helpers.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/state_helpers.ts @@ -189,6 +189,14 @@ export function validateColumn( const invalidMessages: string[] = []; + if (annotation.timeField == null || annotation.timeField === '') { + invalidMessages.push( + i18n.translate('xpack.lens.xyChart.annotationError.timeFieldEmpty', { + defaultMessage: 'Time field is missing', + }) + ); + } + if (annotation.timeField && !Boolean(layerDataView.getFieldByName(annotation.timeField))) { invalidMessages.push( i18n.translate('xpack.lens.xyChart.annotationError.timeFieldNotFound', { diff --git a/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts b/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts index 124e7e8e1ddd3..d66d49e1ba9bd 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts @@ -19,6 +19,7 @@ import { coreMock, themeServiceMock } from '@kbn/core/public/mocks'; import { LegendSize } from '@kbn/visualizations-plugin/common'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; describe('#toExpression', () => { const xyVisualization = getXyVisualization({ @@ -30,6 +31,7 @@ describe('#toExpression', () => { core: coreMock.createStart(), storage: {} as IStorageWrapper, data: dataPluginMock.createStartContract(), + unifiedSearch: unifiedSearchPluginMock.createStartContract(), }); let mockDatasource: ReturnType; let frame: ReturnType; @@ -560,6 +562,7 @@ describe('#toExpression', () => { layerType: layerTypes.ANNOTATIONS, annotations: [], indexPatternId: 'my-indexPattern', + ignoreGlobalFilters: true, }, ], }, diff --git a/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts b/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts index bfe722a21f979..345e8ffcb5b19 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts @@ -190,6 +190,7 @@ export const buildExpression = ( annotations: layer.annotations.map((c) => ({ ...c, label: uniqueLabels[c.id], + ignoreGlobalFilters: layer.ignoreGlobalFilters, })), }; }); diff --git a/x-pack/plugins/lens/public/visualizations/xy/types.ts b/x-pack/plugins/lens/public/visualizations/xy/types.ts index 203718e4b9f8c..6a953ad452fc3 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/types.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/types.ts @@ -82,7 +82,7 @@ export interface YConfig { color?: string; icon?: string; lineWidth?: number; - lineStyle?: LineStyle; + lineStyle?: Exclude; fill?: FillStyle; iconPosition?: IconPosition; textVisibility?: boolean; @@ -119,6 +119,7 @@ export interface XYAnnotationLayerConfig { hide?: boolean; indexPatternId: string; simpleView?: boolean; + ignoreGlobalFilters: boolean; } export type XYLayerConfig = diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts index febec4fb08af4..818df64fba94f 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts @@ -30,6 +30,7 @@ import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { DataViewsState } from '../../state_management'; import { createMockedIndexPattern } from '../../indexpattern_datasource/mocks'; import { createMockDataViewsState } from '../../data_views_service/mocks'; +import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; const exampleAnnotation: EventAnnotationConfig = { id: 'an1', @@ -81,6 +82,7 @@ const xyVisualization = getXyVisualization({ core: coreMock.createStart(), storage: {} as IStorageWrapper, data: dataPluginMock.createStartContract(), + unifiedSearch: unifiedSearchPluginMock.createStartContract(), }); describe('xy_visualization', () => { @@ -464,6 +466,7 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], + ignoreGlobalFilters: true, }, ], }, @@ -475,6 +478,7 @@ describe('xy_visualization', () => { layerId: 'annotation', layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', + ignoreGlobalFilters: true, annotations: [ exampleAnnotation, { @@ -502,6 +506,7 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], + ignoreGlobalFilters: true, }, ], }, @@ -525,6 +530,7 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2, { ...exampleAnnotation2, id: 'newColId' }], + ignoreGlobalFilters: true, }); }); it('should reorder a dimension to a annotation layer', () => { @@ -539,6 +545,7 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation, exampleAnnotation2], + ignoreGlobalFilters: true, }, ], }, @@ -563,6 +570,7 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2, exampleAnnotation], + ignoreGlobalFilters: true, }); }); @@ -578,12 +586,14 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], + ignoreGlobalFilters: true, }, { layerId: 'second', layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], + ignoreGlobalFilters: true, }, ], }, @@ -609,12 +619,14 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], + ignoreGlobalFilters: true, }, { layerId: 'second', layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [{ ...exampleAnnotation, id: 'an2' }], + ignoreGlobalFilters: true, }, ]); }); @@ -630,12 +642,14 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], + ignoreGlobalFilters: true, }, { layerId: 'second', layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], + ignoreGlobalFilters: true, }, ], }, @@ -661,12 +675,14 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], + ignoreGlobalFilters: true, }, { layerId: 'second', layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], + ignoreGlobalFilters: true, }, ]); }); @@ -682,12 +698,14 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], + ignoreGlobalFilters: true, }, { layerId: 'second', layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], + ignoreGlobalFilters: true, }, ], }, @@ -713,12 +731,14 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [], + ignoreGlobalFilters: true, }, { layerId: 'second', layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], + ignoreGlobalFilters: true, }, ]); }); @@ -734,12 +754,14 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], + ignoreGlobalFilters: true, }, { layerId: 'second', layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [], + ignoreGlobalFilters: true, }, ], }, @@ -765,12 +787,14 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [], + ignoreGlobalFilters: true, }, { layerId: 'second', layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], + ignoreGlobalFilters: true, }, ]); }); @@ -851,6 +875,7 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation, { ...exampleAnnotation, id: 'an2' }], + ignoreGlobalFilters: true, }, ], }, @@ -870,6 +895,7 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], + ignoreGlobalFilters: true, }, ]); }); @@ -1590,6 +1616,7 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation], + ignoreGlobalFilters: true, }, ], }; @@ -2236,6 +2263,18 @@ describe('xy_visualization', () => { const errors = xyVisualization.getErrorMessages(xyState, getFrameMock()); expect(errors).toHaveLength(4); }); + it('should contain error if current annotation contains no time field set', () => { + const xyState = createStateWithAnnotationProps({ + timeField: undefined, + }); + const errors = xyVisualization.getErrorMessages(xyState, getFrameMock()); + expect(errors).toHaveLength(1); + expect(errors![0]).toEqual( + expect.objectContaining({ + shortMessage: expect.stringContaining('Time field is missing'), + }) + ); + }); }); }); @@ -2407,6 +2446,7 @@ describe('xy_visualization', () => { layerId: 'annotation', layerType: layerTypes.ANNOTATIONS, annotations: [exampleAnnotation2], + ignoreGlobalFilters: true, }, ], }, @@ -2427,6 +2467,7 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], + ignoreGlobalFilters: true, }, ], }); @@ -2444,6 +2485,7 @@ describe('xy_visualization', () => { layerId: 'annotation', layerType: layerTypes.ANNOTATIONS, annotations: [exampleAnnotation2], + ignoreGlobalFilters: true, }, ], }, @@ -2464,6 +2506,7 @@ describe('xy_visualization', () => { layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', annotations: [exampleAnnotation2], + ignoreGlobalFilters: true, }, ], }); diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx index fe3af3d813b33..093b47278d3cf 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx @@ -18,6 +18,7 @@ import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-pl import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { getSuggestions } from './xy_suggestions'; import { XyToolbar } from './xy_config_panel'; import { DimensionEditor } from './xy_config_panel/dimension_editor'; @@ -93,6 +94,7 @@ export const getXyVisualization = ({ useLegacyTimeAxis, kibanaTheme, eventAnnotationService, + unifiedSearch, }: { core: CoreStart; storage: IStorageWrapper; @@ -102,6 +104,7 @@ export const getXyVisualization = ({ fieldFormats: FieldFormatsStart; useLegacyTimeAxis: boolean; kibanaTheme: ThemeServiceStart; + unifiedSearch: UnifiedSearchPublicPluginStart; }): Visualization => ({ id: XY_ID, visualizationTypes, @@ -522,6 +525,7 @@ export const getXyVisualization = ({ savedObjects: core.savedObjects, docLinks: core.docLinks, http: core.http, + unifiedSearch, }} > {dimensionEditor} diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization_helpers.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization_helpers.tsx index 0e32e4006187c..7aaa1c93f8fdb 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization_helpers.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization_helpers.tsx @@ -286,6 +286,7 @@ const newLayerFn = { layerType: layerTypes.ANNOTATIONS, annotations: [], indexPatternId, + ignoreGlobalFilters: true, }), }; diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/annotations_panel.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/annotations_panel.tsx index 5d68e29a88d08..95e30fa529127 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/annotations_panel.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/annotations_panel.tsx @@ -17,7 +17,12 @@ import { isQueryAnnotationConfig, isRangeAnnotationConfig, } from '@kbn/event-annotation-plugin/public'; -import { QueryPointEventAnnotationConfig } from '@kbn/event-annotation-plugin/common'; +import { + EventAnnotationConfig, + PointInTimeEventAnnotationConfig, + QueryPointEventAnnotationConfig, +} from '@kbn/event-annotation-plugin/common'; +import moment from 'moment'; import { FieldOption, FieldOptionValue, @@ -70,23 +75,20 @@ export const AnnotationsPanel = ( const isRange = isRangeAnnotationConfig(currentAnnotation); const [queryInputShouldOpen, setQueryInputShouldOpen] = React.useState(false); useEffect(() => { - if (isQueryBased) { - setQueryInputShouldOpen(false); - } else { - setQueryInputShouldOpen(true); - } + setQueryInputShouldOpen(!isQueryBased); }, [isQueryBased]); const setAnnotations = useCallback( - (annotation) => { + (annotation: Partial | undefined) => { if (annotation == null) { return; } const newConfigs = [...(localLayer.annotations || [])]; const existingIndex = newConfigs.findIndex((c) => c.id === accessor); if (existingIndex !== -1) { + const existingConfig = newConfigs[existingIndex]; newConfigs[existingIndex] = sanitizeProperties({ - ...newConfigs[existingIndex], + ...existingConfig, ...annotation, }); } else { @@ -138,26 +140,43 @@ export const AnnotationsPanel = ( ]} idSelected={`lens_xyChart_annotation_${currentAnnotation?.type}`} onChange={(id) => { - const typeFromId = id.replace('lens_xyChart_annotation_', ''); + const typeFromId = id.replace( + 'lens_xyChart_annotation_', + '' + ) as EventAnnotationConfig['type']; if (currentAnnotation?.type === typeFromId) { return; } - if (currentAnnotation?.key.type === 'range') { - setAnnotations({ + if (typeFromId === 'query') { + const currentIndexPattern = + frame.dataViews.indexPatterns[localLayer.indexPatternId]; + // If coming from a range type, it requires some additional resets + const additionalRangeResets = isRangeAnnotationConfig(currentAnnotation) + ? { + label: + currentAnnotation.label === defaultRangeAnnotationLabel + ? defaultAnnotationLabel + : currentAnnotation.label, + color: toLineAnnotationColor(currentAnnotation.color), + } + : {}; + return setAnnotations({ type: typeFromId, - label: - currentAnnotation.label === defaultRangeAnnotationLabel - ? defaultAnnotationLabel - : currentAnnotation.label, - color: toLineAnnotationColor(currentAnnotation.color), + timeField: + (currentIndexPattern.timeFieldName || + // fallback to the first avaiable date field in the dataView + currentIndexPattern.fields.find(({ type: fieldType }) => fieldType === 'date') + ?.displayName) ?? + '', key: { type: 'point_in_time' }, - }); - } else { - setAnnotations({ - type: typeFromId, - key: currentAnnotation?.key, + ...additionalRangeResets, }); } + // From query to manual annotation + return setAnnotations({ + type: typeFromId, + key: { type: 'point_in_time', timestamp: moment().toISOString() }, + }); }} isFullWidth /> @@ -264,6 +283,7 @@ export const AnnotationsPanel = ( } }} fieldIsInvalid={!fieldIsValid} + data-test-subj="lnsXY-annotation-query-based-text-decoration-field-picker" autoFocus={!selectedField} /> diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/index.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/index.test.tsx index 5514e3062213e..049823e3361ce 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/index.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/index.test.tsx @@ -19,6 +19,8 @@ import moment from 'moment'; import { EventAnnotationConfig } from '@kbn/event-annotation-plugin/common'; import { createMockDataViewsState } from '../../../../data_views_service/mocks'; import { createMockedIndexPattern } from '../../../../indexpattern_datasource/mocks'; +import { act } from 'react-dom/test-utils'; +import { EuiButtonGroup } from '@elastic/eui'; jest.mock('lodash', () => { const original = jest.requireActual('lodash'); @@ -29,6 +31,12 @@ jest.mock('lodash', () => { }; }); +jest.mock('@kbn/unified-search-plugin/public', () => ({ + QueryStringInput: () => { + return 'QueryStringInput'; + }, +})); + const customLineStaticAnnotation: EventAnnotationConfig = { id: 'ann1', type: 'manual', @@ -55,6 +63,7 @@ describe('AnnotationsPanel', () => { layerId: 'annotation', indexPatternId: 'indexPattern1', annotations: [customLineStaticAnnotation], + ignoreGlobalFilters: true, }, ], }; @@ -129,6 +138,7 @@ describe('AnnotationsPanel', () => { layerId: 'annotation', layerType: 'annotations', indexPatternId: 'indexPattern1', + ignoreGlobalFilters: true, }; const component = mount( { indexPatternId: 'indexPattern1', layerId: 'annotation', layerType: 'annotations', + ignoreGlobalFilters: true, }, ], }); @@ -231,6 +242,7 @@ describe('AnnotationsPanel', () => { indexPatternId: 'indexPattern1', layerId: 'annotation', layerType: 'annotations', + ignoreGlobalFilters: true, }, ], }); @@ -260,6 +272,7 @@ describe('AnnotationsPanel', () => { layerId: 'annotation', layerType: 'annotations', indexPatternId: indexPattern.id, + ignoreGlobalFilters: true, }; const frameMock = createMockFramePublicAPI({ datasourceLayers: {}, @@ -284,16 +297,16 @@ describe('AnnotationsPanel', () => { ); expect( - component.find('[data-test-subj="annotation-query-based-field-picker"]').exists() + component.find('[data-test-subj="lnsXY-annotation-query-based-field-picker"]').exists() ).toBeTruthy(); expect( - component.find('[data-test-subj="annotation-query-based-query-input"]').exists() + component.find('[data-test-subj="lnsXY-annotation-query-based-query-input"]').exists() ).toBeTruthy(); // The provided indexPattern has 2 date fields expect( component - .find('[data-test-subj="annotation-query-based-field-picker"]') + .find('[data-test-subj="lnsXY-annotation-query-based-field-picker"]') .at(0) .prop('options') ).toHaveLength(2); @@ -305,5 +318,111 @@ describe('AnnotationsPanel', () => { component.find('[data-test-subj="lnsXY-annotation-tooltip-add_field"]').exists() ).toBeTruthy(); }); + + test('should prefill timeField with the default time field when switching to query based annotations', () => { + const state = testState(); + const indexPattern = createMockedIndexPattern(); + state.layers[0] = { + annotations: [customLineStaticAnnotation], + layerId: 'annotation', + layerType: 'annotations', + ignoreGlobalFilters: true, + indexPatternId: indexPattern.id, + }; + const frameMock = createMockFramePublicAPI({ + datasourceLayers: {}, + dataViews: createMockDataViewsState({ + indexPatterns: { [indexPattern.id]: indexPattern }, + }), + }); + + const setState = jest.fn(); + + const component = mount( + + ); + + act(() => { + component + .find(`[data-test-subj="lns-xyAnnotation-placementType"]`) + .find(EuiButtonGroup) + .prop('onChange')!('lens_xyChart_annotation_query'); + }); + component.update(); + + expect(setState).toHaveBeenCalledWith( + expect.objectContaining({ + layers: [ + expect.objectContaining({ + annotations: [expect.objectContaining({ timeField: 'timestamp' })], + }), + ], + }) + ); + }); + + test('should fallback to the first date field available in the dataView if not time-based', () => { + const state = testState(); + const indexPattern = createMockedIndexPattern({ timeFieldName: '' }); + state.layers[0] = { + annotations: [customLineStaticAnnotation], + layerId: 'annotation', + layerType: 'annotations', + ignoreGlobalFilters: true, + indexPatternId: indexPattern.id, + }; + const frameMock = createMockFramePublicAPI({ + datasourceLayers: {}, + dataViews: createMockDataViewsState({ + indexPatterns: { [indexPattern.id]: indexPattern }, + }), + }); + + const setState = jest.fn(); + + const component = mount( + + ); + + act(() => { + component + .find(`[data-test-subj="lns-xyAnnotation-placementType"]`) + .find(EuiButtonGroup) + .prop('onChange')!('lens_xyChart_annotation_query'); + }); + component.update(); + + expect(setState).toHaveBeenCalledWith( + expect.objectContaining({ + layers: [ + expect.objectContaining({ + annotations: [expect.objectContaining({ timeField: 'timestampLabel' })], + }), + ], + }) + ); + }); }); }); diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/manual_annotation_panel.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/manual_annotation_panel.tsx index 33b04b17b1a2f..795d076f22f0f 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/manual_annotation_panel.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/manual_annotation_panel.tsx @@ -26,7 +26,7 @@ export const ConfigPanelManualAnnotation = ({ datatableUtilities, }: { annotation?: ManualEventAnnotationType | undefined; - onChange: (annotations: Partial | undefined) => void; + onChange: (annotation: Partial | undefined) => void; datatableUtilities: DatatableUtilitiesService; frame: FramePublicAPI; state: XYState; diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/query_annotation_panel.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/query_annotation_panel.tsx index 69cd398c562bf..f6c44080bad81 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/query_annotation_panel.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/query_annotation_panel.tsx @@ -42,7 +42,7 @@ export const ConfigPanelQueryAnnotation = ({ }) => { const currentIndexPattern = frame.dataViews.indexPatterns[layer.indexPatternId]; const currentExistingFields = frame.dataViews.existingFields[currentIndexPattern.title]; - // list only supported field by operation, remove the rest + // list only date fields const options = currentIndexPattern.fields .filter((field) => field.type === 'date' && field.displayName) .map((field) => { @@ -84,6 +84,7 @@ export const ConfigPanelQueryAnnotation = ({ onChange={(query: Query) => { onChange({ filter: { type: 'kibana_query', ...query } }); }} + data-test-subj="lnsXY-annotation-query-based-query-input" indexPattern={currentIndexPattern} /> @@ -114,7 +115,7 @@ export const ConfigPanelQueryAnnotation = ({ } }} fieldIsInvalid={!fieldIsValid} - data-test-subj="annotation-query-based-field-picker" + data-test-subj="lnsXY-annotation-query-based-field-picker" /> diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/range_annotation_panel.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/range_annotation_panel.tsx index edecd94e3c8e8..1bed2d760514b 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/range_annotation_panel.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/range_annotation_panel.tsx @@ -39,7 +39,7 @@ export const ConfigPanelApplyAsRangeSwitch = ({ }: { annotation?: ManualEventAnnotationType; datatableUtilities: DatatableUtilitiesService; - onChange: (annotations: Partial | undefined) => void; + onChange: (annotations: Partial | undefined) => void; frame: FramePublicAPI; state: XYState; }) => { diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/tooltip_annotation_panel.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/tooltip_annotation_panel.tsx index c8ea7a0ed2ece..586d83b993b9c 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/tooltip_annotation_panel.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/annotations_config_panel/tooltip_annotation_panel.tsx @@ -198,6 +198,7 @@ export function TooltipSection({ onFieldSelectChange(choice, index); }} fieldIsInvalid={!fieldIsValid} + data-test-subj={`lnsXY-annotation-tooltip-field-picker--${index}`} autoFocus={isNew && value == null} /> diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/shared/line_style_settings.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/shared/line_style_settings.tsx index af3c5a2a297cf..a479daeb75919 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/shared/line_style_settings.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/shared/line_style_settings.tsx @@ -19,7 +19,7 @@ import { LineStyle } from '@kbn/expression-xy-plugin/common'; import { idPrefix } from '../dimension_editor'; interface LineStyleConfig { - lineStyle?: LineStyle; + lineStyle?: Exclude; lineWidth?: number; } @@ -86,7 +86,7 @@ export const LineStyleSettings = ({ ]} idSelected={`${idPrefix}${currentConfig?.lineStyle || 'solid'}`} onChange={(id) => { - const newMode = id.replace(idPrefix, '') as LineStyle; + const newMode = id.replace(idPrefix, '') as Exclude; setConfig({ lineStyle: newMode }); }} isIconOnly diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts index a2a0463a41cc4..68061bdca0113 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_suggestions.test.ts @@ -24,6 +24,7 @@ import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks' import { coreMock, themeServiceMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; jest.mock('../../id_generator'); @@ -36,6 +37,7 @@ const xyVisualization = getXyVisualization({ core: coreMock.createStart(), storage: {} as IStorageWrapper, data: dataPluginMock.createStartContract(), + unifiedSearch: unifiedSearchPluginMock.createStartContract(), }); describe('xy_suggestions', () => { @@ -550,6 +552,7 @@ describe('xy_suggestions', () => { layerId: 'second', layerType: layerTypes.ANNOTATIONS, indexPatternId: 'indexPattern1', + ignoreGlobalFilters: true, annotations: [ { id: '1', diff --git a/x-pack/plugins/lens/server/embeddable/make_lens_embeddable_factory.ts b/x-pack/plugins/lens/server/embeddable/make_lens_embeddable_factory.ts index bbac982150000..8b977fbce5742 100644 --- a/x-pack/plugins/lens/server/embeddable/make_lens_embeddable_factory.ts +++ b/x-pack/plugins/lens/server/embeddable/make_lens_embeddable_factory.ts @@ -27,7 +27,7 @@ import { commonUpdateVisLayerType, getLensCustomVisualizationMigrations, getLensFilterMigrations, - commonExplicitAnnotationType, + commonEnrichAnnotationLayer, getLensDataViewMigrations, commonMigrateMetricIds, commonMigratePartitionChartGroups, @@ -141,7 +141,7 @@ export const makeLensEmbeddableFactory = }; let migratedLensState = commonMigrateMetricIds(lensState.attributes); - migratedLensState = commonExplicitAnnotationType( + migratedLensState = commonEnrichAnnotationLayer( migratedLensState as LensDocShape850 ); migratedLensState = commonMigratePartitionChartGroups( diff --git a/x-pack/plugins/lens/server/migrations/common_migrations.ts b/x-pack/plugins/lens/server/migrations/common_migrations.ts index 89dca4829c583..7fd65d44bb0b6 100644 --- a/x-pack/plugins/lens/server/migrations/common_migrations.ts +++ b/x-pack/plugins/lens/server/migrations/common_migrations.ts @@ -423,7 +423,7 @@ export const commonFixValueLabelsInXY = ( }; }; -export const commonExplicitAnnotationType = ( +export const commonEnrichAnnotationLayer = ( attributes: LensDocShape850 ): LensDocShape850 => { // Skip the migration heavy part if not XY or it does not contain annotations @@ -449,6 +449,7 @@ export const commonExplicitAnnotationType = ( return { ...l, annotations: l.annotations.map((a) => ({ ...a, type: 'manual' })), + ignoreGlobalFilters: true, }; }), }, diff --git a/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts b/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts index b07f801e53f11..8264ed4853d35 100644 --- a/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts +++ b/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts @@ -2294,7 +2294,7 @@ describe('Lens migrations', () => { }); }); - describe('8.5.0 Add Annotation event type and dataView references', () => { + describe('8.5.0 Add Annotation event type and ignore filters flag', () => { const context = { log: { warn: () => {} } } as unknown as SavedObjectMigrationContext; const example = { type: 'lens', @@ -2328,6 +2328,7 @@ describe('Lens migrations', () => { expect(annotationLayer).toEqual({ layerType: 'annotations', annotations: [{ id: 'annotation-id', type: 'manual' }], + ignoreGlobalFilters: true, }); }); }); diff --git a/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts b/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts index e48f46ad885c5..2f02ca358fcc0 100644 --- a/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts +++ b/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts @@ -55,7 +55,7 @@ import { commonFixValueLabelsInXY, commonLockOldMetricVisSettings, commonPreserveOldLegendSizeDefault, - commonExplicitAnnotationType, + commonEnrichAnnotationLayer, getLensDataViewMigrations, commonMigrateMetricIds, commonMigratePartitionChartGroups, @@ -520,12 +520,12 @@ const preserveOldLegendSizeDefault: SavedObjectMigrationFn ({ ...doc, attributes: commonPreserveOldLegendSizeDefault(doc.attributes) }); -const addEventAnnotationType: SavedObjectMigrationFn< +const enrichAnnotationLayers: SavedObjectMigrationFn< LensDocShape850, LensDocShape850 > = (doc) => { const newDoc = cloneDeep(doc); - return { ...newDoc, attributes: commonExplicitAnnotationType(newDoc.attributes) }; + return { ...newDoc, attributes: commonEnrichAnnotationLayer(newDoc.attributes) }; }; const migrateMetricIds: SavedObjectMigrationFn = (doc) => ({ @@ -565,7 +565,7 @@ const lensMigrations: SavedObjectMigrationMap = { enhanceTableRowHeight ), '8.3.0': flow(lockOldMetricVisSettings, preserveOldLegendSizeDefault, fixValueLabelsInXY), - '8.5.0': flow(migrateMetricIds, addEventAnnotationType, migratePartitionChartGroups), + '8.5.0': flow(migrateMetricIds, enrichAnnotationLayers, migratePartitionChartGroups), }; export const getAllMigrations = ( diff --git a/x-pack/plugins/lens/server/migrations/types.ts b/x-pack/plugins/lens/server/migrations/types.ts index a922223b6ccd7..87c993a712e01 100644 --- a/x-pack/plugins/lens/server/migrations/types.ts +++ b/x-pack/plugins/lens/server/migrations/types.ts @@ -294,6 +294,7 @@ export interface XYVisState850 { layerId: string; layerType: Extract; annotations: Array<{ id: string; type: 'manual' | 'query' }>; + ignoreGlobalFilters: boolean; } >; } diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts index 5774a46684644..41f88de18720c 100644 --- a/x-pack/plugins/maps/public/kibana_services.ts +++ b/x-pack/plugins/maps/public/kibana_services.ts @@ -59,6 +59,7 @@ export const getCoreI18n = () => coreStart.i18n; export const getSearchService = () => pluginsStart.data.search; export const getEmbeddableService = () => pluginsStart.embeddable; export const getNavigateToApp = () => coreStart.application.navigateToApp; +export const getUrlForApp = () => coreStart.application.getUrlForApp; export const getNavigateToUrl = () => coreStart.application.navigateToUrl; export const getSavedObjectsTagging = () => pluginsStart.savedObjectsTagging; export const getPresentationUtilContext = () => pluginsStart.presentationUtil.ContextProvider; @@ -66,7 +67,6 @@ export const getSecurityService = () => pluginsStart.security; export const getSpacesApi = () => pluginsStart.spaces; export const getTheme = () => coreStart.theme; export const getUsageCollection = () => pluginsStart.usageCollection; -export const getApplication = () => coreStart.application; export const isScreenshotMode = () => { return pluginsStart.screenshotMode ? pluginsStart.screenshotMode.isScreenshotMode() : false; }; diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts index cdf61a6ab9173..a2f4f4004797e 100644 --- a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts +++ b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts @@ -6,8 +6,9 @@ */ import { DataViewsContract } from '@kbn/data-views-plugin/common'; -import { AppMountParameters } from '@kbn/core/public'; +import { AppMountParameters, CoreStart } from '@kbn/core/public'; import { IContainer } from '@kbn/embeddable-plugin/public'; +import type { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public'; import { LayerDescriptor } from '../../common/descriptor_types'; import type { MapEmbeddableConfig, @@ -29,7 +30,14 @@ export interface LazyLoadedMapModules { ) => MapEmbeddableType; getIndexPatternService: () => DataViewsContract; getMapsCapabilities: () => any; - renderApp: (params: AppMountParameters, AppUsageTracker: React.FC) => Promise<() => void>; + renderApp: ( + params: AppMountParameters, + deps: { + coreStart: CoreStart; + AppUsageTracker: React.FC; + savedObjectsTagging?: SavedObjectTaggingPluginStart; + } + ) => Promise<() => void>; createSecurityLayerDescriptors: ( indexPatternId: string, indexPatternTitle: string diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 8a0f85393587b..9890676f2cec5 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -190,10 +190,11 @@ export class MapsPlugin euiIconType: APP_ICON_SOLUTION, category: DEFAULT_APP_CATEGORIES.kibana, async mount(params: AppMountParameters) { + const [coreStart, { savedObjectsTagging }] = await core.getStartServices(); const UsageTracker = plugins.usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment; const { renderApp } = await lazyLoadMapModules(); - return renderApp(params, UsageTracker); + return renderApp(params, { coreStart, AppUsageTracker: UsageTracker, savedObjectsTagging }); }, }); diff --git a/x-pack/plugins/maps/public/render_app.tsx b/x-pack/plugins/maps/public/render_app.tsx index 3deea0f82b272..196332d5e873e 100644 --- a/x-pack/plugins/maps/public/render_app.tsx +++ b/x-pack/plugins/maps/public/render_app.tsx @@ -9,14 +9,17 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Router, Switch, Route, Redirect, RouteComponentProps } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; -import type { AppMountParameters } from '@kbn/core/public'; +import type { CoreStart, AppMountParameters } from '@kbn/core/public'; import { ExitFullScreenButtonKibanaProvider } from '@kbn/shared-ux-button-exit-full-screen'; -import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaThemeProvider, toMountPoint } from '@kbn/kibana-react-plugin/public'; import { createKbnUrlStateStorage, withNotifyOnErrors, IKbnUrlStateStorage, } from '@kbn/kibana-utils-plugin/public'; +import { FormattedRelative } from '@kbn/i18n-react'; +import type { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-plugin/public'; +import { TableListViewKibanaProvider } from '@kbn/content-management-table-list'; import { getCoreChrome, getCoreI18n, @@ -67,7 +70,15 @@ function setAppChrome() { export async function renderApp( { element, history, onAppLeave, setHeaderActionMenu, theme$ }: AppMountParameters, - AppUsageTracker: React.FC + { + coreStart, + AppUsageTracker, + savedObjectsTagging, + }: { + coreStart: CoreStart; + savedObjectsTagging?: SavedObjectTaggingPluginStart; + AppUsageTracker: React.FC; + } ) { goToSpecifiedPath = (path) => history.push(path); kbnUrlStateStorage = createKbnUrlStateStorage({ @@ -117,27 +128,36 @@ export async function renderApp( - - - - - // Redirect other routes to list, or if hash-containing, their non-hash equivalents - { - if (hash) { - // Remove leading hash - const newPath = hash.substr(1); - return ; - } else if (pathname === '/' || pathname === '') { - return ; - } else { - return ; - } - }} - /> - - + + + + + + // Redirect other routes to list, or if hash-containing, their non-hash equivalents + { + if (hash) { + // Remove leading hash + const newPath = hash.substr(1); + return ; + } else if (pathname === '/' || pathname === '') { + return ; + } else { + return ; + } + }} + /> + + + , diff --git a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx index 9278f08bd4d2d..e9c3102e2aa85 100644 --- a/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx +++ b/x-pack/plugins/maps/public/routes/list_page/maps_list_view.tsx @@ -5,25 +5,24 @@ * 2.0. */ -import React, { MouseEvent } from 'react'; +import React from 'react'; import { SavedObjectReference } from '@kbn/core/types'; +import type { SavedObjectsFindOptionsReference } from '@kbn/core/public'; +import { METRIC_TYPE } from '@kbn/analytics'; import { i18n } from '@kbn/i18n'; -import { EuiLink } from '@elastic/eui'; -import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/basic_table'; -import { TableListView } from '@kbn/kibana-react-plugin/public'; +import { TableListView } from '@kbn/content-management-table-list'; +import type { UserContentCommonSchema } from '@kbn/content-management-table-list'; +import { SimpleSavedObject } from '@kbn/core-saved-objects-api-browser'; import { goToSpecifiedPath } from '../../render_app'; import { APP_ID, getEditPath, MAP_PATH, MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; import { getMapsCapabilities, - getToasts, getCoreChrome, getExecutionContext, getNavigateToApp, getSavedObjectsClient, - getSavedObjectsTagging, getUiSettings, - getTheme, - getApplication, + getUsageCollection, } from '../../kibana_services'; import { getAppTitle } from '../../../common/i18n_getters'; import { MapSavedObjectAttributes } from '../../../common/map_saved_object_type'; @@ -38,62 +37,35 @@ interface MapItem { references?: SavedObjectReference[]; } -const savedObjectsTagging = getSavedObjectsTagging(); -const searchFilters = savedObjectsTagging - ? [savedObjectsTagging.ui.getSearchBarFilter({ useName: true })] - : []; - -const tableColumns: Array> = [ - { - field: 'title', - name: i18n.translate('xpack.maps.mapListing.titleFieldTitle', { - defaultMessage: 'Title', - }), - sortable: true, - render: (field: string, record: MapItem) => ( - { - e.preventDefault(); - goToSpecifiedPath(getEditPath(record.id)); - }} - data-test-subj={`mapListingTitleLink-${record.title.split(' ').join('-')}`} - > - {field} - - ), - }, - { - field: 'description', - name: i18n.translate('xpack.maps.mapListing.descriptionFieldTitle', { - defaultMessage: 'Description', - }), - dataType: 'string', - sortable: true, - }, -]; -if (savedObjectsTagging) { - tableColumns.push(savedObjectsTagging.ui.getTableColumnDefinition()); +interface MapUserContent extends UserContentCommonSchema { + type: string; + attributes: { + title: string; + }; } function navigateToNewMap() { const navigateToApp = getNavigateToApp(); + getUsageCollection()?.reportUiCounter(APP_ID, METRIC_TYPE.CLICK, 'create_maps_vis_editor'); navigateToApp(APP_ID, { path: MAP_PATH, }); } -async function findMaps(searchQuery: string) { - let searchTerm = searchQuery; - let tagReferences; - - if (savedObjectsTagging) { - const parsed = savedObjectsTagging.ui.parseSearchQuery(searchQuery, { - useName: true, - }); - searchTerm = parsed.searchTerm; - tagReferences = parsed.tagReferences; - } +const toTableListViewSavedObject = ( + savedObject: SimpleSavedObject +): MapUserContent => { + return { + ...savedObject, + updatedAt: savedObject.updatedAt!, + attributes: { + ...savedObject.attributes, + title: savedObject.attributes.title ?? '', + }, + }; +}; +async function findMaps(searchTerm: string, tagReferences?: SavedObjectsFindOptionsReference[]) { const resp = await getSavedObjectsClient().find({ type: MAP_SAVED_OBJECT_TYPE, search: searchTerm ? `${searchTerm}*` : undefined, @@ -107,15 +79,7 @@ async function findMaps(searchQuery: string) { return { total: resp.total, - hits: resp.savedObjects.map((savedObject) => { - return { - id: savedObject.id, - title: savedObject.attributes.title, - description: savedObject.attributes.description, - references: savedObject.references, - updatedAt: savedObject.updatedAt, - }; - }), + hits: resp.savedObjects.map(toTableListViewSavedObject), }; } @@ -141,13 +105,12 @@ export function MapsListView() { getCoreChrome().setBreadcrumbs([{ text: getAppTitle() }]); return ( - + id="map" headingId="mapsListingPage" - rowHeader="title" createItem={isReadOnly ? undefined : navigateToNewMap} findItems={findMaps} deleteItems={isReadOnly ? undefined : deleteMaps} - tableColumns={tableColumns} listingLimit={listingLimit} initialFilter={''} initialPageSize={initialPageSize} @@ -157,14 +120,8 @@ export function MapsListView() { entityNamePlural={i18n.translate('xpack.maps.mapListing.entityNamePlural', { defaultMessage: 'maps', })} - tableCaption={i18n.translate('xpack.maps.mapListing.tableCaption', { - defaultMessage: 'Maps', - })} tableListTitle={getAppTitle()} - toastNotifications={getToasts()} - searchFilters={searchFilters} - theme={getTheme()} - application={getApplication()} + onClickTitle={({ id }) => goToSpecifiedPath(getEditPath(id))} /> ); } diff --git a/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts b/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts index ae104c08034ff..f073c7335eb09 100644 --- a/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts +++ b/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts @@ -9,6 +9,7 @@ import uuid from 'uuid/v4'; import { i18n } from '@kbn/i18n'; import type { Query } from '@kbn/es-query'; import type { SerializableRecord } from '@kbn/utility-types'; +import { METRIC_TYPE } from '@kbn/analytics'; import { createAction, ACTION_VISUALIZE_GEO_FIELD, @@ -50,8 +51,8 @@ export const visualizeGeoFieldAction = createAction({ const usageCollection = getUsageCollection(); usageCollection?.reportUiCounter( APP_ID, - 'visualize_geo_field', - context.originatingApp ? context.originatingApp : 'unknownOriginatingApp' + METRIC_TYPE.CLICK, + `create_maps_vis_${context.originatingApp ? context.originatingApp : 'unknownOriginatingApp'}` ); getCore().application.navigateToApp(app, { diff --git a/x-pack/plugins/ml/common/constants/locator.ts b/x-pack/plugins/ml/common/constants/locator.ts index 9e9e4b875bf32..395d23b15a16b 100644 --- a/x-pack/plugins/ml/common/constants/locator.ts +++ b/x-pack/plugins/ml/common/constants/locator.ts @@ -52,9 +52,12 @@ export const ML_PAGES = { FILTER_LISTS_EDIT: 'settings/filter_lists/edit_filter_list', ACCESS_DENIED: 'access-denied', OVERVIEW: 'overview', + NOTIFICATIONS: 'notifications', AIOPS: 'aiops', AIOPS_EXPLAIN_LOG_RATE_SPIKES: 'aiops/explain_log_rate_spikes', AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT: 'aiops/explain_log_rate_spikes_index_select', + AIOPS_LOG_CATEGORIZATION: 'aiops/log_categorization', + AIOPS_LOG_CATEGORIZATION_INDEX_SELECT: 'aiops/log_categorization_index_select', } as const; export type MlPages = typeof ML_PAGES[keyof typeof ML_PAGES]; diff --git a/x-pack/plugins/ml/common/constants/message_levels.ts b/x-pack/plugins/ml/common/constants/message_levels.ts index fd6cef75174ae..b4a69aaf29bbf 100644 --- a/x-pack/plugins/ml/common/constants/message_levels.ts +++ b/x-pack/plugins/ml/common/constants/message_levels.ts @@ -11,3 +11,5 @@ export const MESSAGE_LEVEL = { SUCCESS: 'success', WARNING: 'warning', } as const; + +export type MessageLevel = typeof MESSAGE_LEVEL[keyof typeof MESSAGE_LEVEL]; diff --git a/x-pack/plugins/ml/common/types/locator.ts b/x-pack/plugins/ml/common/types/locator.ts index adb873a3d7c6b..973b2bb7335a3 100644 --- a/x-pack/plugins/ml/common/types/locator.ts +++ b/x-pack/plugins/ml/common/types/locator.ts @@ -63,7 +63,9 @@ export type MlGenericUrlState = MLPageState< | typeof ML_PAGES.DATA_VISUALIZER_INDEX_SELECT | typeof ML_PAGES.AIOPS | typeof ML_PAGES.AIOPS_EXPLAIN_LOG_RATE_SPIKES - | typeof ML_PAGES.AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT, + | typeof ML_PAGES.AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT + | typeof ML_PAGES.AIOPS_LOG_CATEGORIZATION + | typeof ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT, MlGenericUrlPageState | undefined >; @@ -269,6 +271,7 @@ export type MlLocatorState = | CalendarEditUrlState | FilterEditUrlState | MlGenericUrlState + | NotificationsUrlState | TrainedModelsUrlState | TrainedModelsNodesUrlState; @@ -285,3 +288,12 @@ export type TrainedModelsNodesUrlState = MLPageState< typeof ML_PAGES.TRAINED_MODELS_NODES, TrainedModelsNodesQueryState | undefined >; + +export interface NotificationsQueryState { + level: string; +} + +export type NotificationsUrlState = MLPageState< + typeof ML_PAGES.NOTIFICATIONS, + NotificationsQueryState | undefined +>; diff --git a/x-pack/plugins/ml/common/types/notifications.ts b/x-pack/plugins/ml/common/types/notifications.ts new file mode 100644 index 0000000000000..4fef15b868bf2 --- /dev/null +++ b/x-pack/plugins/ml/common/types/notifications.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { MessageLevel } from '../constants/message_levels'; + +export interface NotificationsQueryParams { + level?: MessageLevel; + type?: string; + size?: number; + from?: number; + sortField?: string; + sortDirection?: string; + queryString?: string; + earliest?: string; + latest?: string; +} + +export interface NotificationSource { + message: string; + job_id: string; + level: MessageLevel; + timestamp: number; + node_name: string; + job_type: string; +} + +export interface NotificationsSearchResponse { + total: number; + results: Array< + NotificationSource & { + id: string; + } + >; +} + +export type NotificationItem = NotificationsSearchResponse['results'][number]; + +export interface NotificationsCountQueryParams { + lastCheckedAt: number; +} + +export type NotificationsCountResponse = { + [key in MessageLevel]: number; +}; diff --git a/x-pack/plugins/ml/common/types/storage.ts b/x-pack/plugins/ml/common/types/storage.ts index d1708ae17776b..b59772fac9151 100644 --- a/x-pack/plugins/ml/common/types/storage.ts +++ b/x-pack/plugins/ml/common/types/storage.ts @@ -7,15 +7,12 @@ import { EntityFieldType } from './anomalies'; -export const ML_ENTITY_FIELDS_CONFIG = 'ml.singleMetricViewer.partitionFields'; - +export const ML_ENTITY_FIELDS_CONFIG = 'ml.singleMetricViewer.partitionFields' as const; export const ML_APPLY_TIME_RANGE_CONFIG = 'ml.jobSelectorFlyout.applyTimeRange'; - export const ML_GETTING_STARTED_CALLOUT_DISMISSED = 'ml.gettingStarted.isDismissed'; - export const ML_FROZEN_TIER_PREFERENCE = 'ml.frozenDataTierPreference'; - export const ML_ANOMALY_EXPLORER_PANELS = 'ml.anomalyExplorerPanels'; +export const ML_NOTIFICATIONS_LAST_CHECKED_AT = 'ml.notificationsLastCheckedAt'; export type PartitionFieldConfig = | { @@ -58,8 +55,23 @@ export type MlStorage = Partial<{ [ML_ENTITY_FIELDS_CONFIG]: PartitionFieldsConfig; [ML_APPLY_TIME_RANGE_CONFIG]: ApplyTimeRangeConfig; [ML_GETTING_STARTED_CALLOUT_DISMISSED]: boolean | undefined; - [ML_FROZEN_TIER_PREFERENCE]: 'exclude_frozen' | 'include_frozen'; + [ML_FROZEN_TIER_PREFERENCE]: 'exclude-frozen' | 'include-frozen'; [ML_ANOMALY_EXPLORER_PANELS]: AnomalyExplorerPanelsState | undefined; + [ML_NOTIFICATIONS_LAST_CHECKED_AT]: number | undefined; }> | null; export type MlStorageKey = keyof Exclude; + +export type TMlStorageMapped = T extends typeof ML_ENTITY_FIELDS_CONFIG + ? PartitionFieldsConfig + : T extends typeof ML_APPLY_TIME_RANGE_CONFIG + ? ApplyTimeRangeConfig + : T extends typeof ML_GETTING_STARTED_CALLOUT_DISMISSED + ? boolean | undefined + : T extends typeof ML_FROZEN_TIER_PREFERENCE + ? 'exclude-frozen' | 'include-frozen' | undefined + : T extends typeof ML_ANOMALY_EXPLORER_PANELS + ? AnomalyExplorerPanelsState | undefined + : T extends typeof ML_NOTIFICATIONS_LAST_CHECKED_AT + ? number | undefined + : null; diff --git a/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx b/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx new file mode 100644 index 0000000000000..e1d816d61357a --- /dev/null +++ b/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { pick } from 'lodash'; + +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n-react'; +import { LogCategorization } from '@kbn/aiops-plugin/public'; + +import { useMlContext } from '../contexts/ml'; +import { useMlKibana } from '../contexts/kibana'; +import { HelpMenu } from '../components/help_menu'; +import { TechnicalPreviewBadge } from '../components/technical_preview_badge'; + +import { MlPageHeader } from '../components/page_header'; + +export const LogCategorizationPage: FC = () => { + const { services } = useMlKibana(); + + const context = useMlContext(); + const dataView = context.currentDataView; + const savedSearch = context.currentSavedSearch; + + return ( + <> + + + + + + + + + + + {dataView && ( + + )} + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx index e87c8c47df5a2..06bb873971ba0 100644 --- a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx +++ b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx @@ -53,7 +53,7 @@ export const FullTimeRangeSelector: FC = ({ dataView, query, disabled, ca } const [isPopoverOpen, setPopover] = useState(false); - const [frozenDataPreference, setFrozenDataPreference] = useStorage( + const [frozenDataPreference, setFrozenDataPreference] = useStorage( ML_FROZEN_TIER_PREFERENCE, FROZEN_TIER_PREFERENCE.EXCLUDE ); diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx b/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx index a33530551a285..79d33ef9cd2ab 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx +++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx @@ -29,7 +29,7 @@ import { } from './job_selector_flyout'; import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; import { useStorage } from '../../contexts/ml/use_storage'; -import { ApplyTimeRangeConfig, ML_APPLY_TIME_RANGE_CONFIG } from '../../../../common/types/storage'; +import { ML_APPLY_TIME_RANGE_CONFIG } from '../../../../common/types/storage'; interface GroupObj { groupId: string; @@ -89,7 +89,7 @@ export interface JobSelectionMaps { export function JobSelector({ dateFormatTz, singleSelection, timeseriesOnly }: JobSelectorProps) { const [globalState, setGlobalState] = useUrlState('_g'); - const [applyTimeRangeConfig, setApplyTimeRangeConfig] = useStorage( + const [applyTimeRangeConfig, setApplyTimeRangeConfig] = useStorage( ML_APPLY_TIME_RANGE_CONFIG, true ); diff --git a/x-pack/plugins/ml/public/application/components/ml_page/notifications_indicator.tsx b/x-pack/plugins/ml/public/application/components/ml_page/notifications_indicator.tsx new file mode 100644 index 0000000000000..4591222e4ffcd --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/ml_page/notifications_indicator.tsx @@ -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 React, { FC } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiFlexGroup, EuiFlexItem, EuiNotificationBadge } from '@elastic/eui'; + +export const NotificationsIndicator: FC = () => { + return ( + + + + + {false ? ( + + {0} + + ) : null} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx b/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx index b87a128c0c788..4dbc2400aa7cf 100644 --- a/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx +++ b/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx @@ -7,8 +7,9 @@ import { i18n } from '@kbn/i18n'; import type { EuiSideNavItemType } from '@elastic/eui'; -import { useCallback, useMemo } from 'react'; +import React, { ReactNode, useCallback, useMemo } from 'react'; import { AIOPS_ENABLED } from '@kbn/aiops-plugin/common'; +import { NotificationsIndicator } from './notifications_indicator'; import type { MlLocatorParams } from '../../../../common/types/locator'; import { useUrlState } from '../../util/url_state'; import { useMlLocator, useNavigateToPath } from '../../contexts/kibana'; @@ -19,7 +20,7 @@ import { checkPermission } from '../../capabilities/check_capabilities'; export interface Tab { id: string; - name: string; + name: ReactNode; disabled?: boolean; items?: Tab[]; testSubj?: string; @@ -80,6 +81,13 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) { disabled: disableLinks, testSubj: 'mlMainTab overview', }, + { + id: 'notifications', + pathId: ML_PAGES.NOTIFICATIONS, + name: , + disabled: disableLinks, + testSubj: 'mlMainTab notifications', + }, ], }, { @@ -226,7 +234,7 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) { mlTabs.push({ id: 'aiops_section', name: i18n.translate('xpack.ml.navMenu.aiopsTabLinkText', { - defaultMessage: 'AIOps', + defaultMessage: 'AIOps Labs', }), disabled: disableLinks, items: [ @@ -234,11 +242,20 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) { id: 'explainlogratespikes', pathId: ML_PAGES.AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT, name: i18n.translate('xpack.ml.navMenu.explainLogRateSpikesLinkText', { - defaultMessage: 'Explain log rate spikes', + defaultMessage: 'Explain Log Rate Spikes', }), disabled: disableLinks, testSubj: 'mlMainTab explainLogRateSpikes', }, + { + id: 'logCategorization', + pathId: ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT, + name: i18n.translate('xpack.ml.navMenu.logCategorizationLinkText', { + defaultMessage: 'Log Pattern Analysis', + }), + disabled: disableLinks, + testSubj: 'mlMainTab logCategorization', + }, ], }); } diff --git a/x-pack/plugins/ml/public/application/contexts/ml/use_storage.ts b/x-pack/plugins/ml/public/application/contexts/ml/use_storage.ts index d41dfedfa32ee..761434a7bb3b4 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/use_storage.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/use_storage.ts @@ -6,25 +6,40 @@ */ import { useCallback, useState } from 'react'; +import { isDefined } from '../../../../common/types/guards'; import { useMlKibana } from '../kibana'; import type { MlStorageKey } from '../../../../common/types/storage'; +import { TMlStorageMapped } from '../../../../common/types/storage'; /** * Hook for accessing and changing a value in the storage. * @param key - Storage key * @param initValue */ -export function useStorage(key: MlStorageKey, initValue?: T): [T, (value: T) => void] { +export function useStorage>( + key: K, + initValue?: T +): [ + typeof initValue extends undefined + ? TMlStorageMapped + : Exclude, undefined>, + (value: TMlStorageMapped) => void +] { const { services: { storage }, } = useMlKibana(); - const [val, setVal] = useState(storage.get(key) ?? initValue); + const [val, setVal] = useState(storage.get(key) ?? initValue); - const setStorage = useCallback((value: T): void => { + const setStorage = useCallback((value: TMlStorageMapped): void => { try { - storage.set(key, value); - setVal(value); + if (isDefined(value)) { + storage.set(key, value); + setVal(value); + } else { + storage.remove(key); + setVal(initValue); + } } catch (e) { throw new Error('Unable to update storage with provided value'); } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index f9d4f08d649e4..dbcff279def12 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -187,7 +187,7 @@ export const DataFrameAnalyticsList: FC = ({ ); const { onTableChange, pagination, sorting } = useTableSettings( - filteredAnalytics, + filteredAnalytics.length, pageState, updatePageState ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts index 993aadd3f810f..457749df67035 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Direction, EuiBasicTableProps, Pagination, PropertySort } from '@elastic/eui'; +import { Direction, EuiBasicTableProps, Pagination } from '@elastic/eui'; import { useCallback, useMemo } from 'react'; import { ListingPageUrlState } from '../../../../../../../common/types/common'; @@ -33,11 +33,16 @@ export interface CriteriaWithPagination extends Criteria { interface UseTableSettingsReturnValue { onTableChange: EuiBasicTableProps['onChange']; pagination: Pagination; - sorting: { sort: PropertySort }; + sorting: { + sort: { + field: keyof T; + direction: 'asc' | 'desc'; + }; + }; } export function useTableSettings( - items: TypeOfItem[], + totalItemCount: number, pageState: ListingPageUrlState, updatePageState: (update: Partial) => void ): UseTableSettingsReturnValue { @@ -45,10 +50,15 @@ export function useTableSettings( const onTableChange: EuiBasicTableProps['onChange'] = useCallback( ({ page, sort }: CriteriaWithPagination) => { + let resultSortField = sort?.field; + if (typeof resultSortField !== 'string') { + resultSortField = pageState.sortField as keyof TypeOfItem; + } + const result = { pageIndex: page?.index ?? pageState.pageIndex, pageSize: page?.size ?? pageState.pageSize, - sortField: (sort?.field as string) ?? pageState.sortField, + sortField: resultSortField as string, sortDirection: sort?.direction ?? pageState.sortDirection, }; updatePageState(result); @@ -60,16 +70,16 @@ export function useTableSettings( () => ({ pageIndex, pageSize, - totalItemCount: items.length, + totalItemCount, pageSizeOptions: PAGE_SIZE_OPTIONS, }), - [items, pageIndex, pageSize] + [totalItemCount, pageIndex, pageSize] ); const sorting = useMemo( () => ({ sort: { - field: sortField as string, + field: sortField as keyof TypeOfItem, direction: sortDirection as Direction, }, }), diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.tsx b/x-pack/plugins/ml/public/application/explorer/explorer.tsx index 32a4f613c22d4..b9feffb1ec116 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer.tsx +++ b/x-pack/plugins/ml/public/application/explorer/explorer.tsx @@ -77,10 +77,7 @@ import { useToastNotificationService } from '../services/toast_notification_serv import { useMlKibana, useMlLocator } from '../contexts/kibana'; import { useMlContext } from '../contexts/ml'; import { useAnomalyExplorerContext } from './anomaly_explorer_context'; -import { - AnomalyExplorerPanelsState, - ML_ANOMALY_EXPLORER_PANELS, -} from '../../../common/types/storage'; +import { ML_ANOMALY_EXPLORER_PANELS } from '../../../common/types/storage'; import { useStorage } from '../contexts/ml/use_storage'; interface ExplorerPageProps { @@ -173,8 +170,9 @@ export const Explorer: FC = ({ }) => { const isMobile = useIsWithinBreakpoints(['xs', 's']); - const [anomalyExplorerPanelState, setAnomalyExplorerPanelState] = - useStorage(ML_ANOMALY_EXPLORER_PANELS, { + const [anomalyExplorerPanelState, setAnomalyExplorerPanelState] = useStorage( + ML_ANOMALY_EXPLORER_PANELS, + { topInfluencers: { isCollapsed: false, size: 20, @@ -182,7 +180,8 @@ export const Explorer: FC = ({ mainPage: { size: 80, }, - }); + } + ); const topInfluencersPanelRef = useRef(null); diff --git a/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx b/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx new file mode 100644 index 0000000000000..745fc318c3404 --- /dev/null +++ b/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx @@ -0,0 +1,341 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useCallback, useMemo, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiBadge, + EuiCallOut, + EuiInMemoryTable, + EuiSearchBar, + EuiSpacer, + IconColor, + Query, + SearchFilterConfig, +} from '@elastic/eui'; +import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/basic_table'; +import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common'; +import useDebounce from 'react-use/lib/useDebounce'; +import { SavedObjectsWarning } from '../../components/saved_objects_warning'; +import { useTimeRangeUpdates } from '../../contexts/kibana/use_timefilter'; +import { useToastNotificationService } from '../../services/toast_notification_service'; +import { useFieldFormatter } from '../../contexts/kibana/use_field_formatter'; +import { useRefresh } from '../../routing/use_refresh'; +import { useTableSettings } from '../../data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings'; +import { MESSAGE_LEVEL, MessageLevel } from '../../../../common/constants/message_levels'; +import { ListingPageUrlState } from '../../../../common/types/common'; +import { usePageUrlState } from '../../util/url_state'; +import { ML_PAGES } from '../../../../common/constants/locator'; +import type { NotificationItem } from '../../../../common/types/notifications'; +import { useMlKibana } from '../../contexts/kibana'; + +const levelBadgeMap: Record = { + error: 'danger', + info: 'default', + success: 'subdued', + warning: 'warning', +}; + +export const getDefaultNotificationsListState = (): ListingPageUrlState => ({ + pageIndex: 0, + pageSize: 25, + sortField: 'timestamp', + sortDirection: 'desc', +}); + +export const NotificationsList: FC = () => { + const { + services: { + mlServices: { mlApiServices }, + }, + } = useMlKibana(); + const { displayErrorToast } = useToastNotificationService(); + + const timeRange = useTimeRangeUpdates(); + + const [isLoading, setIsLoading] = useState(false); + const [items, setItems] = useState([]); + const [totalCount, setTotalCount] = useState(0); + const [queryError, setQueryError] = useState(''); + + const dateFormatter = useFieldFormatter(FIELD_FORMAT_IDS.DATE); + + const [pageState, updatePageState] = usePageUrlState( + ML_PAGES.NOTIFICATIONS, + getDefaultNotificationsListState() + ); + + const { onTableChange, pagination, sorting } = useTableSettings( + totalCount, + pageState, + updatePageState + ); + + const refresh = useRefresh(); + + const searchQueryText = pageState.queryText; + + const queryInstance = useMemo(() => { + try { + setQueryError(''); + return EuiSearchBar.Query.parse(searchQueryText ?? ''); + } catch (error) { + setQueryError(error.message); + } + }, [searchQueryText, setQueryError]); + + const fetchNotifications = useCallback(async () => { + if (!queryInstance) return; + + const queryString = EuiSearchBar.Query.toESQueryString(queryInstance); + + try { + setIsLoading(true); + const response = await mlApiServices.notifications.findMessages({ + sortField: sorting.sort!.field, + sortDirection: sorting.sort!.direction, + earliest: timeRange.from, + latest: timeRange.to, + queryString, + }); + setItems(response.results); + setTotalCount(response.total); + } catch (error) { + displayErrorToast( + error, + i18n.translate('xpack.ml.notifications.fetchFailedError', { + defaultMessage: 'Fetch notifications failed', + }) + ); + } + + setIsLoading(false); + }, [sorting, queryInstance, mlApiServices.notifications, displayErrorToast, timeRange]); + + useDebounce( + function refetchNotification() { + fetchNotifications(); + }, + 500, + [sorting, queryInstance, refresh] + ); + + const columns: Array> = [ + { + field: 'timestamp', + name: , + sortable: true, + truncateText: false, + 'data-test-subj': 'mlNotificationTime', + width: '250px', + render: (v: number) => dateFormatter(v), + }, + { + field: 'level', + name: , + sortable: true, + truncateText: false, + 'data-test-subj': 'mlNotificationLabel', + render: (value: MessageLevel) => { + return {value}; + }, + width: '100px', + }, + { + field: 'job_type', + name: , + sortable: true, + truncateText: false, + 'data-test-subj': 'mlNotificationType', + render: (value: MessageLevel) => { + return {value}; + }, + width: '200px', + }, + { + field: 'job_id', + name: , + sortable: true, + truncateText: false, + 'data-test-subj': 'mlNotificationEntity', + width: '200px', + }, + { + field: 'message', + name: , + sortable: false, + truncateText: false, + 'data-test-subj': 'mlNotificationMessage', + }, + ]; + + const filters: SearchFilterConfig[] = useMemo(() => { + return [ + { + type: 'field_value_selection', + field: 'level', + name: i18n.translate('xpack.ml.notifications.filters.level.name', { + defaultMessage: 'Level', + }), + multiSelect: 'or', + options: [ + { + value: MESSAGE_LEVEL.ERROR, + name: i18n.translate('xpack.ml.notifications.filters.level.error', { + defaultMessage: 'Error', + }), + field: 'level', + }, + { + value: MESSAGE_LEVEL.WARNING, + name: i18n.translate('xpack.ml.notifications.filters.level.warning', { + defaultMessage: 'Warning', + }), + field: 'level', + }, + { + value: MESSAGE_LEVEL.INFO, + name: i18n.translate('xpack.ml.notifications.filters.level.info', { + defaultMessage: 'Info', + }), + field: 'level', + }, + ], + }, + { + type: 'field_value_selection', + field: 'job_type', + name: i18n.translate('xpack.ml.notifications.filters.level.type', { + defaultMessage: 'Type', + }), + multiSelect: 'or', + options: [ + { + value: 'anomaly_detector', + name: i18n.translate('xpack.ml.notifications.filters.type.anomalyDetector', { + defaultMessage: 'Anomaly Detection', + }), + }, + { + value: 'data_frame_analytics', + name: i18n.translate('xpack.ml.notifications.filters.type.dfa', { + defaultMessage: 'Data Frame Analytics', + }), + }, + { + value: 'inference', + name: i18n.translate('xpack.ml.notifications.filters.type.inference', { + defaultMessage: 'Inference', + }), + }, + { + value: 'system', + name: i18n.translate('xpack.ml.notifications.filters.type.system', { + defaultMessage: 'System', + }), + }, + ], + }, + ]; + }, []); + + return ( + <> + + + { + updatePageState({ queryText: e.queryText }); + }} + data-test-subj={'mlNotificationsSearchBar'} + /> + + + + {queryError ? ( + <> + + } + color="danger" + iconType="alert" + > +

{queryError}

+
+ + + ) : null} + + + columns={columns} + hasActions={false} + isExpandable={false} + isSelectable={false} + items={items} + itemId={'id'} + loading={isLoading} + rowProps={(item) => ({ + 'data-test-subj': `mlNotificationsTableRow row-${item.id}`, + })} + pagination={pagination} + onChange={onTableChange} + sorting={sorting} + data-test-subj={isLoading ? 'mlNotificationsTable loading' : 'mlNotificationsTable loaded'} + message={ + + } + /> + + ); +}; diff --git a/x-pack/plugins/ml/public/application/notifications/page.tsx b/x-pack/plugins/ml/public/application/notifications/page.tsx new file mode 100644 index 0000000000000..80d052bf818da --- /dev/null +++ b/x-pack/plugins/ml/public/application/notifications/page.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { NotificationsList } from './components/notifications_list'; +import { useMlKibana, useTimefilter } from '../contexts/kibana'; +import { MlPageHeader } from '../components/page_header'; +import { NodeAvailableWarning } from '../components/node_available_warning'; +import { UpgradeWarning } from '../components/upgrade'; +import { HelpMenu } from '../components/help_menu'; + +export const NotificationsPage: FC = () => { + const { + services: { docLinks }, + } = useMlKibana(); + const helpLink = docLinks.links.ml.guide; + + useTimefilter({ timeRangeSelector: true, autoRefreshSelector: true }); + + return ( +
+ + + + + + + + + + +
+ ); +}; + +// required for dynamic import using React.lazy() +// eslint-disable-next-line import/no-default-export +export default NotificationsPage; diff --git a/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts b/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts index 38ace0233cbb8..9ae337cfcd7f0 100644 --- a/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts +++ b/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts @@ -55,13 +55,36 @@ export const DATA_VISUALIZER_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ href: '/datavisualizer', }); -export const AIOPS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ +// we need two AIOPS_BREADCRUMB breadcrumb items as they each need to link +// to either the explain log rate spikes page or the log categorization page +export const AIOPS_BREADCRUMB_EXPLAIN_LOG_RATE_SPIKES: ChromeBreadcrumb = Object.freeze({ text: i18n.translate('xpack.ml.aiopsBreadcrumbLabel', { - defaultMessage: 'AIOps', + defaultMessage: 'AIOps Labs', }), href: '/aiops/explain_log_rate_spikes_index_select', }); +export const AIOPS_BREADCRUMB_LOG_PATTERN_ANALYSIS: ChromeBreadcrumb = Object.freeze({ + text: i18n.translate('xpack.ml.aiopsBreadcrumbLabel', { + defaultMessage: 'AIOps Labs', + }), + href: '/aiops/log_categorization_index_select', +}); + +export const EXPLAIN_LOG_RATE_SPIKES: ChromeBreadcrumb = Object.freeze({ + text: i18n.translate('xpack.ml.aiops.explainLogRateSpikesBreadcrumbLabel', { + defaultMessage: 'Explain Log Rate Spikes', + }), + href: '/aiops/explain_log_rate_spikes_index_select', +}); + +export const LOG_PATTERN_ANALYSIS: ChromeBreadcrumb = Object.freeze({ + text: i18n.translate('xpack.ml.aiops.logPatternAnalysisBreadcrumbLabel', { + defaultMessage: 'Log Pattern Analysis', + }), + href: '/aiops/log_categorization_index_select', +}); + export const CREATE_JOB_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ text: i18n.translate('xpack.ml.createJobsBreadcrumbLabel', { defaultMessage: 'Create job', @@ -90,7 +113,10 @@ const breadcrumbs = { DATA_FRAME_ANALYTICS_BREADCRUMB, TRAINED_MODELS, DATA_VISUALIZER_BREADCRUMB, - AIOPS_BREADCRUMB, + AIOPS_BREADCRUMB_EXPLAIN_LOG_RATE_SPIKES, + AIOPS_BREADCRUMB_LOG_PATTERN_ANALYSIS, + EXPLAIN_LOG_RATE_SPIKES, + LOG_PATTERN_ANALYSIS, CREATE_JOB_BREADCRUMB, CALENDAR_MANAGEMENT_BREADCRUMB, FILTER_LISTS_BREADCRUMB, diff --git a/x-pack/plugins/ml/public/application/routing/routes/aiops/explain_log_rate_spikes.tsx b/x-pack/plugins/ml/public/application/routing/routes/aiops/explain_log_rate_spikes.tsx index 5fac891a79675..e101efff47a2b 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/aiops/explain_log_rate_spikes.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/aiops/explain_log_rate_spikes.tsx @@ -35,7 +35,11 @@ export const explainLogRateSpikesRouteFactory = ( render: (props, deps) => , breadcrumbs: [ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), - getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB', navigateToPath, basePath), + getBreadcrumbWithUrlForApp( + 'AIOPS_BREADCRUMB_EXPLAIN_LOG_RATE_SPIKES', + navigateToPath, + basePath + ), { text: i18n.translate('xpack.ml.aiopsBreadcrumbs.explainLogRateSpikesLabel', { defaultMessage: 'Explain log rate spikes', diff --git a/x-pack/plugins/ml/public/application/routing/routes/aiops/index.ts b/x-pack/plugins/ml/public/application/routing/routes/aiops/index.ts index f2b192a4cd097..5b55d41e887bc 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/aiops/index.ts +++ b/x-pack/plugins/ml/public/application/routing/routes/aiops/index.ts @@ -6,3 +6,4 @@ */ export * from './explain_log_rate_spikes'; +export * from './log_categorization'; diff --git a/x-pack/plugins/ml/public/application/routing/routes/aiops/log_categorization.tsx b/x-pack/plugins/ml/public/application/routing/routes/aiops/log_categorization.tsx new file mode 100644 index 0000000000000..f08c2973a6f8b --- /dev/null +++ b/x-pack/plugins/ml/public/application/routing/routes/aiops/log_categorization.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { parse } from 'query-string'; + +import { i18n } from '@kbn/i18n'; + +import { AIOPS_ENABLED } from '@kbn/aiops-plugin/common'; + +import { NavigateToPath } from '../../../contexts/kibana'; + +import { MlRoute, PageLoader, PageProps } from '../../router'; +import { useResolver } from '../../use_resolver'; +import { LogCategorizationPage as Page } from '../../../aiops/log_categorization'; + +import { checkBasicLicense } from '../../../license'; +import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities'; +import { cacheDataViewsContract } from '../../../util/index_utils'; +import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; + +export const logCategorizationRouteFactory = ( + navigateToPath: NavigateToPath, + basePath: string +): MlRoute => ({ + id: 'log_categorization', + path: '/aiops/log_categorization', + title: i18n.translate('xpack.ml.aiops.logCategorization.docTitle', { + defaultMessage: 'Log Pattern Analysis', + }), + render: (props, deps) => , + breadcrumbs: [ + getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), + getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_LOG_PATTERN_ANALYSIS', navigateToPath, basePath), + { + text: i18n.translate('xpack.ml.aiops.logCategorization.docTitle', { + defaultMessage: 'Log Pattern Analysis', + }), + }, + ], + disabled: !AIOPS_ENABLED, +}); + +const PageWrapper: FC = ({ location, deps }) => { + const { redirectToMlAccessDeniedPage } = deps; + + const { index, savedSearchId }: Record = parse(location.search, { sort: false }); + const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, { + checkBasicLicense, + cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract), + checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), + }); + + return ( + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/routing/routes/index.ts b/x-pack/plugins/ml/public/application/routing/routes/index.ts index 12ddc39e0e23e..3b481ab6cde59 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/index.ts +++ b/x-pack/plugins/ml/public/application/routing/routes/index.ts @@ -16,3 +16,4 @@ export { timeSeriesExplorerRouteFactory } from './timeseriesexplorer'; export * from './explorer'; export * from './access_denied'; export * from './trained_models'; +export * from './notifications'; diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx index 5ea3bfa9d35eb..9323d86d32b99 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx @@ -45,17 +45,29 @@ const getDataVisBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath), { text: i18n.translate('xpack.ml.jobsBreadcrumbs.selectDateViewLabel', { - defaultMessage: 'Data View', + defaultMessage: 'Select Data View', }), }, ]; const getExplainLogRateSpikesBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [ getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), - getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB', navigateToPath, basePath), + getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_EXPLAIN_LOG_RATE_SPIKES', navigateToPath, basePath), + getBreadcrumbWithUrlForApp('EXPLAIN_LOG_RATE_SPIKES', navigateToPath, basePath), { text: i18n.translate('xpack.ml.aiopsBreadcrumbs.selectDateViewLabel', { - defaultMessage: 'Data View', + defaultMessage: 'Select Data View', + }), + }, +]; + +const getLogCategorizationBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [ + getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), + getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_LOG_PATTERN_ANALYSIS', navigateToPath, basePath), + getBreadcrumbWithUrlForApp('LOG_PATTERN_ANALYSIS', navigateToPath, basePath), + { + text: i18n.translate('xpack.ml.aiopsBreadcrumbs.selectDateViewLabel', { + defaultMessage: 'Select Data View', }), }, ]; @@ -116,6 +128,26 @@ export const explainLogRateSpikesIndexOrSearchRouteFactory = ( breadcrumbs: getExplainLogRateSpikesBreadcrumbs(navigateToPath, basePath), }); +export const logCategorizationIndexOrSearchRouteFactory = ( + navigateToPath: NavigateToPath, + basePath: string +): MlRoute => ({ + id: 'data_view_log_categorization', + path: '/aiops/log_categorization_index_select', + title: i18n.translate('xpack.ml.selectDataViewLabel', { + defaultMessage: 'Select Data View', + }), + render: (props, deps) => ( + + ), + breadcrumbs: getLogCategorizationBreadcrumbs(navigateToPath, basePath), +}); + const PageWrapper: FC = ({ nextStepPath, deps, mode }) => { const { services: { diff --git a/x-pack/plugins/ml/public/application/routing/routes/notifications.tsx b/x-pack/plugins/ml/public/application/routing/routes/notifications.tsx new file mode 100644 index 0000000000000..238aacf86bdc7 --- /dev/null +++ b/x-pack/plugins/ml/public/application/routing/routes/notifications.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, { FC, Suspense } from 'react'; +import { i18n } from '@kbn/i18n'; +import { PageLoader, PageProps } from '../router'; +import { useResolver } from '../use_resolver'; +import { checkFullLicense } from '../../license'; +import { checkGetJobsCapabilitiesResolver } from '../../capabilities/check_capabilities'; +import { getMlNodeCount } from '../../ml_nodes_check'; +import { loadMlServerInfo } from '../../services/ml_server_info'; +import { getBreadcrumbWithUrlForApp } from '../breadcrumbs'; +import type { MlRoute } from '..'; +import { NavigateToPath, useTimefilter } from '../../contexts/kibana'; + +const NotificationsPage = React.lazy(() => import('../../notifications/page')); + +export const notificationsRouteFactory = ( + navigateToPath: NavigateToPath, + basePath: string +): MlRoute => ({ + id: 'notifications', + path: '/notifications', + title: i18n.translate('xpack.ml.notifications.notificationsLabel', { + defaultMessage: 'Notifications', + }), + enableDatePicker: true, + render: (props, deps) => , + breadcrumbs: [ + getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), + { + text: i18n.translate('xpack.ml.overview.notificationsLabel', { + defaultMessage: 'Notifications', + }), + }, + ], + 'data-test-subj': 'mlPageNotifications', +}); + +const PageWrapper: FC = ({ deps }) => { + const { redirectToMlAccessDeniedPage } = deps; + + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { + checkFullLicense, + checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), + getMlNodeCount, + loadMlServerInfo, + }); + useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false }); + + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts index 9f3c3a4350a79..dbe41669485fc 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts @@ -44,6 +44,7 @@ import type { DataRecognizerConfigResponse, Module } from '../../../../common/ty import { getHttp } from '../../util/dependency_cache'; import type { RuntimeMappings } from '../../../../common/types/fields'; import type { DatafeedValidationResponse } from '../../../../common/types/job_validation'; +import { notificationsProvider } from './notifications'; export interface MlInfoResponse { defaults: MlServerDefaults; @@ -729,5 +730,6 @@ export function mlApiServicesProvider(httpService: HttpService) { jobs: jobsApiProvider(httpService), savedObjects: savedObjectsApiProvider(httpService), trainedModels: trainedModelsApiProvider(httpService), + notifications: notificationsProvider(httpService), }; } diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/notifications.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/notifications.ts new file mode 100644 index 0000000000000..653ceebc52cda --- /dev/null +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/notifications.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. + */ + +import { omitBy } from 'lodash'; +import { isDefined } from '../../../../common/types/guards'; +import type { + NotificationsQueryParams, + NotificationsSearchResponse, +} from '../../../../common/types/notifications'; +import { basePath } from '.'; +import type { HttpService } from '../http_service'; +import type { + NotificationsCountQueryParams, + NotificationsCountResponse, +} from '../../../../common/types/notifications'; + +export function notificationsProvider(httpService: HttpService) { + const apiBasePath = basePath(); + + return { + findMessages(params: NotificationsQueryParams) { + return httpService.http({ + path: `${apiBasePath}/notifications`, + method: 'GET', + query: omitBy(params, (v) => !isDefined(v)), + }); + }, + + countMessages$(params: NotificationsCountQueryParams) { + return httpService.http$({ + path: `${apiBasePath}/notifications/count`, + method: 'GET', + query: omitBy(params, (v) => !isDefined(v)), + }); + }, + }; +} diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx index e5f3e097b5f54..0df10505e73cc 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx @@ -113,8 +113,7 @@ export const SeriesControls: FC = ({ return getControlsForDetector(selectedDetectorIndex, selectedEntities, selectedJobId); }, [selectedDetectorIndex, selectedEntities, selectedJobId]); - const [storageFieldsConfig, setStorageFieldsConfig] = - useStorage(ML_ENTITY_FIELDS_CONFIG); + const [storageFieldsConfig, setStorageFieldsConfig] = useStorage(ML_ENTITY_FIELDS_CONFIG); // Merge the default config with the one from the local storage const resultFieldsConfig = useMemo(() => { diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx index 8073940aef438..a216436e6c90a 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx @@ -697,7 +697,7 @@ export const ModelsList: FC = ({ : undefined; const { onTableChange, pagination, sorting } = useTableSettings( - items, + items.length, pageState, updatePageState ); diff --git a/x-pack/plugins/ml/public/application/trained_models/nodes_overview/nodes_list.tsx b/x-pack/plugins/ml/public/application/trained_models/nodes_overview/nodes_list.tsx index 9e2480f265dd4..4775642006c20 100644 --- a/x-pack/plugins/ml/public/application/trained_models/nodes_overview/nodes_list.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/nodes_overview/nodes_list.tsx @@ -165,7 +165,7 @@ export const NodesList: FC = ({ compactView = false }) => { }; }, [items]); - let tableSettings: object = useTableSettings(items, pageState, updatePageState); + let tableSettings: object = useTableSettings(items.length, pageState, updatePageState); const search: EuiSearchBarProps = { query: searchQueryText, diff --git a/x-pack/plugins/ml/public/locator/formatters/notifications.ts b/x-pack/plugins/ml/public/locator/formatters/notifications.ts new file mode 100644 index 0000000000000..47103bba91b29 --- /dev/null +++ b/x-pack/plugins/ml/public/locator/formatters/notifications.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 { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { ML_PAGES } from '../../../common/constants/locator'; +import { NotificationsUrlState } from '../../../common/types/locator'; + +export interface NotificationsAppState { + level?: string; +} + +export function formatNotificationsUrl( + appBasePath: string, + pageState: NotificationsUrlState['pageState'] +): string { + let url = `${appBasePath}/${ML_PAGES.NOTIFICATIONS}`; + + if (pageState) { + const appState: NotificationsAppState = {}; + const globalState = {}; + + const { level } = pageState; + if (!!level) { + appState.level = level; + } + + if (isPopulatedObject(globalState)) { + url = setStateToKbnUrl('_g', globalState, { useHash: false, storeInHashQuery: false }, url); + } + if (isPopulatedObject(appState)) { + url = setStateToKbnUrl('_a', appState, { useHash: false, storeInHashQuery: false }, url); + } + } + + return url; +} diff --git a/x-pack/plugins/ml/public/locator/ml_locator.ts b/x-pack/plugins/ml/public/locator/ml_locator.ts index 8281831cb4610..a742700cceaec 100644 --- a/x-pack/plugins/ml/public/locator/ml_locator.ts +++ b/x-pack/plugins/ml/public/locator/ml_locator.ts @@ -6,6 +6,7 @@ */ import type { LocatorDefinition, KibanaLocation } from '@kbn/share-plugin/public'; +import { formatNotificationsUrl } from './formatters/notifications'; import { DataFrameAnalyticsExplorationUrlState, MlLocatorParams, @@ -87,6 +88,8 @@ export class MlLocatorDefinition implements LocatorDefinition { case ML_PAGES.AIOPS: case ML_PAGES.AIOPS_EXPLAIN_LOG_RATE_SPIKES: case ML_PAGES.AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT: + case ML_PAGES.AIOPS_LOG_CATEGORIZATION: + case ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT: case ML_PAGES.OVERVIEW: case ML_PAGES.SETTINGS: case ML_PAGES.FILTER_LISTS_MANAGE: @@ -102,6 +105,9 @@ export class MlLocatorDefinition implements LocatorDefinition { case ML_PAGES.CALENDARS_EDIT: path = formatEditCalendarUrl('', params.pageState); break; + case ML_PAGES.NOTIFICATIONS: + path = formatNotificationsUrl('', params.pageState); + break; default: throw new Error('Page type is not provided or unknown'); diff --git a/x-pack/plugins/ml/server/models/notifications_service/index.ts b/x-pack/plugins/ml/server/models/notifications_service/index.ts new file mode 100644 index 0000000000000..ef47f433da480 --- /dev/null +++ b/x-pack/plugins/ml/server/models/notifications_service/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 { NotificationsService } from './notifications_service_provider'; diff --git a/x-pack/plugins/ml/server/models/notifications_service/notifications_service_provider.ts b/x-pack/plugins/ml/server/models/notifications_service/notifications_service_provider.ts new file mode 100644 index 0000000000000..3a28b86a60b8b --- /dev/null +++ b/x-pack/plugins/ml/server/models/notifications_service/notifications_service_provider.ts @@ -0,0 +1,192 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { IScopedClusterClient } from '@kbn/core/server'; +import type { SearchTotalHits } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { MLSavedObjectService } from '../../saved_objects'; +import type { NotificationItem, NotificationSource } from '../../../common/types/notifications'; +import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../common/constants/index_patterns'; +import type { + MessagesSearchParams, + NotificationsCountParams, +} from '../../routes/schemas/notifications_schema'; +import { NotificationsSearchResponse } from '../../../common/types/notifications'; + +const MAX_NOTIFICATIONS_SIZE = 10000; + +export class NotificationsService { + constructor( + private readonly scopedClusterClient: IScopedClusterClient, + private readonly mlSavedObjectService: MLSavedObjectService + ) {} + + /** + * Searches notifications based on the criteria. + * + * {@link ML_NOTIFICATION_INDEX_PATTERN} uses job_id field for all types of entities, + * e.g. anomaly_detector, data_frame_analytics jobs and inference models, hence + * to make sure the results are space aware, we have to perform separate requests + * for each type of entities. + * + */ + async searchMessages(params: MessagesSearchParams) { + const [adJobIds, dfaJobIds, modelIds] = await Promise.all([ + this.mlSavedObjectService.getAnomalyDetectionJobIds(), + this.mlSavedObjectService.getDataFrameAnalyticsJobIds(), + this.mlSavedObjectService.getTrainedModelsIds(), + ]); + + const results = await Promise.all( + [ + { type: 'anomaly_detector', ids: adJobIds }, + { type: 'data_frame_analytics', ids: dfaJobIds }, + { type: 'inference', ids: modelIds }, + { type: 'system' }, + ] + .filter((v) => v.ids === undefined || v.ids.length > 0) + .map(async (v) => { + const responseBody = + await this.scopedClusterClient.asInternalUser.search( + { + index: ML_NOTIFICATION_INDEX_PATTERN, + ignore_unavailable: true, + from: 0, + size: MAX_NOTIFICATIONS_SIZE, + body: { + sort: [{ [params.sortField]: { order: params.sortDirection } }], + query: { + bool: { + ...(params.queryString + ? { + must: [ + { + query_string: { + query: params.queryString, + default_field: 'message', + }, + }, + ], + } + : {}), + filter: [ + ...(v.ids + ? [ + { + terms: { + job_id: v.ids as string[], + }, + }, + ] + : []), + { + term: { + job_type: { + value: v.type, + }, + }, + }, + ...(params.earliest || params.latest + ? [ + { + range: { + timestamp: { + ...(params.earliest ? { gt: params.earliest } : {}), + ...(params.latest ? { lte: params.latest } : {}), + }, + }, + }, + ] + : []), + ], + }, + }, + }, + }, + { maxRetries: 0 } + ); + + return { + total: (responseBody.hits.total as SearchTotalHits).value, + results: responseBody.hits.hits.map((result) => { + return { + ...result._source, + id: result._id, + }; + }), + } as NotificationsSearchResponse; + }) + ); + + const response = results.reduce( + (acc, curr) => { + acc.total += curr.total; + acc.results = acc.results.concat(curr.results); + return acc; + }, + { total: 0, results: [] } + ); + + function getSortCallback( + sortField: keyof NotificationItem, + sortDirection: 'asc' | 'desc' + ): (a: NotificationItem, b: NotificationItem) => number { + if (sortField === 'timestamp') { + if (sortDirection === 'asc') { + return (a, b) => a.timestamp - b.timestamp; + } else { + return (a, b) => b.timestamp - a.timestamp; + } + } else { + if (sortDirection === 'asc') { + return (a, b) => (a[sortField] ?? '').localeCompare(b[sortField]); + } else { + return (a, b) => (b[sortField] ?? '').localeCompare(a[sortField]); + } + } + } + + response.results = response.results.sort( + getSortCallback(params.sortField, params.sortDirection) + ); + + return response; + } + + /** + * Provides a number of messages by level. + */ + async countMessages(params: NotificationsCountParams) { + const responseBody = await this.scopedClusterClient.asInternalUser.search({ + size: 0, + index: ML_NOTIFICATION_INDEX_PATTERN, + body: { + query: { + bool: { + filter: { + range: { + timestamp: { + gt: params.lastCheckedAt, + }, + }, + }, + }, + }, + aggs: { + by_level: { + terms: { field: 'level' }, + }, + }, + }, + }); + + // @ts-ignore + return responseBody.aggregations.by_level.buckets.reduce((acc, curr) => { + acc[curr.key] = curr.doc_count; + return acc; + }, {}); + } +} diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index cf8b2717522ab..4b03c3beb34e5 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -23,6 +23,7 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; import type { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server'; import type { SpacesPluginSetup } from '@kbn/spaces-plugin/server'; import { FieldFormatsStart } from '@kbn/field-formats-plugin/server'; +import { notificationsRoutes } from './routes/notifications'; import type { PluginsSetup, PluginsStart, RouteInitialization } from './types'; import { PLUGIN_ID } from '../common/constants/app'; import type { MlCapabilities } from '../common/types/capabilities'; @@ -228,6 +229,7 @@ export class MlServerPlugin resolveMlCapabilities, }); trainedModelsRoutes(routeInit); + notificationsRoutes(routeInit); alertingRoutes(routeInit, sharedServicesProviders); initMlServerLog({ log: this.log }); diff --git a/x-pack/plugins/ml/server/routes/apidoc.json b/x-pack/plugins/ml/server/routes/apidoc.json index 0cbc716399545..893f541e6c9f6 100644 --- a/x-pack/plugins/ml/server/routes/apidoc.json +++ b/x-pack/plugins/ml/server/routes/apidoc.json @@ -65,6 +65,10 @@ "SetupModule", "CheckExistingModuleJobs", + "Notifications", + "GetNotifications", + "GetNotificationCounts", + "Annotations", "GetAnnotations", "IndexAnnotations", diff --git a/x-pack/plugins/ml/server/routes/notifications.ts b/x-pack/plugins/ml/server/routes/notifications.ts new file mode 100644 index 0000000000000..5b267e2d39079 --- /dev/null +++ b/x-pack/plugins/ml/server/routes/notifications.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { NotificationsService } from '../models/notifications_service'; +import { + getNotificationsCountQuerySchema, + getNotificationsQuerySchema, +} from './schemas/notifications_schema'; +import { wrapError } from '../client/error_wrapper'; +import { RouteInitialization } from '../types'; + +export function notificationsRoutes({ router, routeGuard }: RouteInitialization) { + /** + * @apiGroup Notifications + * + * @api {get} /api/ml/notifications Get notifications + * @apiName GetNotifications + * @apiDescription Retrieves notifications based on provided criteria. + */ + router.get( + { + path: '/api/ml/notifications', + validate: { + query: getNotificationsQuerySchema, + }, + options: { + tags: [ + 'access:ml:canGetJobs', + 'access:ml:canGetDataFrameAnalytics', + 'access:ml:canGetTrainedModels', + ], + }, + }, + routeGuard.fullLicenseAPIGuard(async ({ client, request, response, mlSavedObjectService }) => { + try { + const notificationsService = new NotificationsService(client, mlSavedObjectService); + + const results = await notificationsService.searchMessages(request.query); + + return response.ok({ + body: results, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + + /** + * @apiGroup Notifications + * + * @api {get} /api/ml/notifications/count Get notification counts + * @apiName GetNotificationCounts + * @apiDescription Counts notifications by level. + */ + router.get( + { + path: '/api/ml/notifications/count', + validate: { + query: getNotificationsCountQuerySchema, + }, + options: { + tags: [ + 'access:ml:canGetJobs', + 'access:ml:canGetDataFrameAnalytics', + 'access:ml:canGetTrainedModels', + ], + }, + }, + routeGuard.fullLicenseAPIGuard(async ({ client, mlSavedObjectService, request, response }) => { + try { + const notificationsService = new NotificationsService(client, mlSavedObjectService); + + const results = await notificationsService.countMessages(request.query); + + return response.ok({ + body: results, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); +} diff --git a/x-pack/plugins/ml/server/routes/schemas/notifications_schema.ts b/x-pack/plugins/ml/server/routes/schemas/notifications_schema.ts new file mode 100644 index 0000000000000..a82691ee52813 --- /dev/null +++ b/x-pack/plugins/ml/server/routes/schemas/notifications_schema.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 { schema, TypeOf } from '@kbn/config-schema'; + +export const getNotificationsQuerySchema = schema.object({ + /** + * Message level, e.g. info, error + */ + level: schema.maybe(schema.string()), + /** + * Message type, e.g. anomaly_detector + */ + type: schema.maybe(schema.string()), + /** + * Search string for the message content + */ + queryString: schema.maybe(schema.string()), + /** + * Page numer, zero-indexed + */ + from: schema.number({ defaultValue: 0 }), + /** + * Number of messages to return + */ + size: schema.number({ defaultValue: 10 }), + /** + * Sort field + */ + sortField: schema.oneOf( + [ + schema.literal('timestamp'), + schema.literal('level'), + schema.literal('job_type'), + schema.literal('job_id'), + ], + { + defaultValue: 'timestamp', + } + ), + /** + * Sort direction + */ + sortDirection: schema.oneOf([schema.literal('asc'), schema.literal('desc')], { + defaultValue: 'desc', + }), + earliest: schema.maybe(schema.string()), + latest: schema.maybe(schema.string()), +}); + +export const getNotificationsCountQuerySchema = schema.object({ + lastCheckedAt: schema.number(), +}); + +export type MessagesSearchParams = TypeOf; + +export type NotificationsCountParams = TypeOf; diff --git a/x-pack/plugins/ml/server/saved_objects/service.ts b/x-pack/plugins/ml/server/saved_objects/service.ts index f473397be9079..d660207e16b89 100644 --- a/x-pack/plugins/ml/server/saved_objects/service.ts +++ b/x-pack/plugins/ml/server/saved_objects/service.ts @@ -637,6 +637,18 @@ export function mlSavedObjectServiceFactory( return models.map((o) => o.attributes[idType]); } + async function getAnomalyDetectionJobIds() { + return _getIds('anomaly-detector', 'job_id'); + } + + async function getDataFrameAnalyticsJobIds() { + return _getIds('data-frame-analytics', 'job_id'); + } + + async function getTrainedModelsIds() { + return _getModelIds('model_id'); + } + async function findTrainedModelsObjectForJobs( jobIds: string[], currentSpaceOnly: boolean = true @@ -744,6 +756,9 @@ export function mlSavedObjectServiceFactory( } return { + getAnomalyDetectionJobIds, + getDataFrameAnalyticsJobIds, + getTrainedModelsIds, getAllJobObjects, getJobObject, createAnomalyDetectionJob, diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index da12e560aa7fc..f2522ea023ec3 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -568,12 +568,12 @@ export const LEGACY_RULES = [ /** * Matches the id for the built-in in email action type - * See x-pack/plugins/actions/server/builtin_action_types/email.ts + * See x-pack/plugins/stack_connectors/server/connector_types/stack/email/index.ts */ export const ALERT_ACTION_TYPE_EMAIL = '.email'; /** * Matches the id for the built-in in log action type - * See x-pack/plugins/actions/server/builtin_action_types/log.ts + * See x-pack/plugins/stack_connectors/server/connector_types/stack/server_log/index.ts */ export const ALERT_ACTION_TYPE_LOG = '.server-log'; diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts index 388ee15b81e6f..78262d806bb46 100644 --- a/x-pack/plugins/observability/common/index.ts +++ b/x-pack/plugins/observability/common/index.ts @@ -12,15 +12,18 @@ export { formatDurationFromTimeUnitChar } from './utils/formatters'; export { ProcessorEvent } from './processor_event'; export { + enableNewSyntheticsView, enableInspectEsQueries, maxSuggestions, enableComparisonByDefault, defaultApmServiceEnvironment, - apmServiceInventoryOptimizedSorting, apmProgressiveLoading, + enableServiceGroups, + apmServiceInventoryOptimizedSorting, apmServiceGroupMaxNumberOfServices, apmTraceExplorerTab, apmOperationsTab, + apmLabsButton, enableInfrastructureHostsView, } from './ui_settings_keys'; diff --git a/x-pack/plugins/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability/common/ui_settings_keys.ts index 18e86eeb55942..d413cd5d79d13 100644 --- a/x-pack/plugins/observability/common/ui_settings_keys.ts +++ b/x-pack/plugins/observability/common/ui_settings_keys.ts @@ -18,4 +18,5 @@ export const apmServiceGroupMaxNumberOfServices = 'observability:apmServiceGroupMaxNumberOfServices'; export const apmTraceExplorerTab = 'observability:apmTraceExplorerTab'; export const apmOperationsTab = 'observability:apmOperationsTab'; +export const apmLabsButton = 'observability:apmLabsButton'; export const enableInfrastructureHostsView = 'observability:enableInfrastructureHostsView'; diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index 43dead397d7d0..9bc1eb7f2a172 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -115,3 +115,5 @@ export { } from './components/shared/exploratory_view/configurations/constants'; export { ExploratoryViewContextProvider } from './components/shared/exploratory_view/contexts/exploratory_view_config'; export { fromQuery, toQuery } from './utils/url'; + +export type { NavigationSection } from './services/navigation_registry'; diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index c8c249426bca5..dad8d5a36bd33 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -51,3 +51,5 @@ export const plugin = (initContext: PluginInitializerContext) => export type { Mappings, ObservabilityPluginSetup, ScopedAnnotationsClient }; export { createOrUpdateIndex, unwrapEsResponse, WrappedElasticsearchClientError }; + +export { uiSettings } from './ui_settings'; diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts index 9c4e310872ef2..6775a7cf51029 100644 --- a/x-pack/plugins/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability/server/ui_settings.ts @@ -21,6 +21,7 @@ import { apmServiceGroupMaxNumberOfServices, apmTraceExplorerTab, apmOperationsTab, + apmLabsButton, enableInfrastructureHostsView, } from '../common/ui_settings_keys'; @@ -31,10 +32,12 @@ const technicalPreviewLabel = i18n.translate( } ); +type UiSettings = UiSettingsParams & { showInLabs?: boolean }; + /** * uiSettings definitions for Observability. */ -export const uiSettings: Record> = { +export const uiSettings: Record = { [enableNewSyntheticsView]: { category: [observabilityFeatureId], name: i18n.translate('xpack.observability.enableNewSyntheticsViewExperimentName', { @@ -61,6 +64,7 @@ export const uiSettings: Record { + describe('observability', () => { + before(() => { + runKbnArchiverScript(ArchiverMethod.LOAD, 'case_observability'); + login(ROLES.soc_manager); + navigateTo('/app/osquery'); + }); + + after(() => { + runKbnArchiverScript(ArchiverMethod.UNLOAD, 'case_observability'); + }); + it('should add result a case and not have add to timeline in result', () => { + cy.react('CustomItemAction', { + props: { index: 1 }, + }).click(); + cy.contains('Live query details'); + cy.contains('Add to Case').click(); + cy.contains('Select case'); + cy.contains(/Select$/).click(); + cy.contains('Test Obs case has been updated'); + cy.visit('/app/observability/cases'); + cy.contains('Test Obs case').click(); + checkResults(); + cy.contains('attached Osquery results'); + cy.contains('SELECT * FROM users;'); + cy.contains('View in Discover').should('exist'); + cy.contains('View in Lens').should('exist'); + cy.contains('Add to Case').should('not.exist'); + cy.contains('Add to timeline investigation').should('not.exist'); + }); + }); + describe('security', () => { + before(() => { + runKbnArchiverScript(ArchiverMethod.LOAD, 'case_security'); + login(ROLES.soc_manager); + navigateTo('/app/osquery'); + }); + + after(() => { + runKbnArchiverScript(ArchiverMethod.UNLOAD, 'case_security'); + }); + + it('should add result a case and have add to timeline in result', () => { + cy.react('CustomItemAction', { + props: { index: 1 }, + }).click(); + cy.contains('Live query details'); + cy.contains('Add to Case').click(); + cy.contains('Select case'); + cy.contains(/Select$/).click(); + cy.contains('Test Security Case has been updated'); + cy.visit('/app/security/cases'); + cy.contains('Test Security Case').click(); + checkResults(); + cy.contains('attached Osquery results'); + cy.contains('SELECT * FROM users;'); + cy.contains('View in Discover').should('exist'); + cy.contains('View in Lens').should('exist'); + cy.contains('Add to Case').should('not.exist'); + cy.contains('Add to timeline investigation').should('exist'); + }); + }); +}); diff --git a/x-pack/plugins/osquery/kibana.json b/x-pack/plugins/osquery/kibana.json index 539c2f7dc18dc..6d956fdce9fe9 100644 --- a/x-pack/plugins/osquery/kibana.json +++ b/x-pack/plugins/osquery/kibana.json @@ -7,8 +7,8 @@ "githubTeam": "security-asset-management" }, "kibanaVersion": "kibana", - "optionalPlugins": ["fleet", "home", "usageCollection", "lens", "telemetry"], - "requiredBundles": ["esUiShared", "fleet", "kibanaUtils", "kibanaReact", "lens"], + "optionalPlugins": ["fleet", "home", "usageCollection", "lens", "telemetry", "cases"], + "requiredBundles": ["esUiShared", "fleet", "kibanaUtils", "kibanaReact", "lens", "cases"], "requiredPlugins": [ "actions", "data", diff --git a/x-pack/plugins/osquery/public/actions/use_live_query_details.ts b/x-pack/plugins/osquery/public/actions/use_live_query_details.ts index b8703a8370d02..dd41eb1455b0e 100644 --- a/x-pack/plugins/osquery/public/actions/use_live_query_details.ts +++ b/x-pack/plugins/osquery/public/actions/use_live_query_details.ts @@ -8,20 +8,17 @@ import { useQuery } from '@tanstack/react-query'; import { i18n } from '@kbn/i18n'; +import { filter } from 'lodash'; import { useKibana } from '../common/lib/kibana'; import type { ESTermQuery } from '../../common/typed_json'; import { useErrorToast } from '../common/hooks/use_error_toast'; -export interface LiveQueryDetailsArgs { - actionDetails: Record; - id: string; -} - interface UseLiveQueryDetails { actionId?: string; isLive?: boolean; filterQuery?: ESTermQuery | string; skip?: boolean; + queryIds?: string[]; } export interface LiveQueryDetailsItem { @@ -56,12 +53,13 @@ export const useLiveQueryDetails = ({ filterQuery, isLive = false, skip = false, + queryIds, // enable finding out specific queries only, eg. in cases }: UseLiveQueryDetails) => { const { http } = useKibana().services; const setErrorToast = useErrorToast(); return useQuery<{ data: LiveQueryDetailsItem }, Error, LiveQueryDetailsItem>( - ['liveQueries', { actionId, filterQuery }], + ['liveQueries', { actionId, filterQuery, queryIds }], () => http.get(`/api/osquery/live_queries/${actionId}`), { enabled: !skip && !!actionId, @@ -73,7 +71,17 @@ export const useLiveQueryDetails = ({ defaultMessage: 'Error while fetching action details', }), }), - select: (response) => response.data, + select: (response) => { + if (queryIds) { + const filteredQueries = filter(response.data.queries, (query) => + queryIds.includes(query.action_id) + ); + + return { ...response.data, queries: filteredQueries }; + } + + return response.data; + }, refetchOnWindowFocus: false, retryDelay: 5000, } diff --git a/x-pack/plugins/osquery/public/cases/add_to_cases_button.tsx b/x-pack/plugins/osquery/public/cases/add_to_cases_button.tsx new file mode 100644 index 0000000000000..9f2df87f35615 --- /dev/null +++ b/x-pack/plugins/osquery/public/cases/add_to_cases_button.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 } from 'react'; +import { CommentType, ExternalReferenceStorageType } from '@kbn/cases-plugin/common'; +import { EuiButtonEmpty, EuiButtonIcon, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import type { CaseAttachmentsWithoutOwner } from '@kbn/cases-plugin/public'; +import { useKibana } from '../common/lib/kibana'; + +const ADD_TO_CASE = i18n.translate( + 'xpack.osquery.pack.queriesTable.addToCaseResultsActionAriaLabel', + { + defaultMessage: 'Add to Case', + } +); + +interface IProps { + queryId: string; + agentIds?: string[]; + actionId: string; + isIcon?: boolean; + isDisabled?: boolean; + iconProps?: Record; +} + +export const AddToCaseButton: React.FC = ({ + actionId, + agentIds = [], + queryId = '', + isIcon = false, + isDisabled, + iconProps, +}) => { + const { cases } = useKibana().services; + + const casePermissions = cases.helpers.canUseCases(); + const hasCasesPermissions = + casePermissions.read && casePermissions.update && casePermissions.push; + const selectCaseModal = cases.hooks.getUseCasesAddToExistingCaseModal({}); + + const handleClick = useCallback(() => { + const attachments: CaseAttachmentsWithoutOwner = [ + { + type: CommentType.externalReference, + externalReferenceId: actionId, + externalReferenceStorage: { + type: ExternalReferenceStorageType.elasticSearchDoc, + }, + externalReferenceAttachmentTypeId: 'osquery', + externalReferenceMetadata: { actionId, agentIds, queryId }, + }, + ]; + if (hasCasesPermissions) { + selectCaseModal.open({ attachments }); + } + }, [actionId, agentIds, hasCasesPermissions, queryId, selectCaseModal]); + + if (isIcon) { + return ( + {ADD_TO_CASE}}> + + + ); + } + + return ( + + {ADD_TO_CASE} + + ); +}; diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index 96afe9deb98e2..98993d69386a5 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -21,6 +21,7 @@ import { useForm as useHookForm, FormProvider } from 'react-hook-form'; import { isEmpty, map, find, pickBy } from 'lodash'; import { i18n } from '@kbn/i18n'; +import type { AddToTimelinePayload } from '../../timelines/get_add_to_timeline'; import type { SavedQuerySOFormData } from '../../saved_queries/form/use_saved_query_form'; import type { EcsMappingFormField, @@ -40,6 +41,7 @@ import { LiveQueryQueryField } from './live_query_query_field'; import { AgentsTableField } from './agents_table_field'; import { PacksComboBoxField } from './packs_combobox_field'; import { savedQueryDataSerializer } from '../../saved_queries/form/use_saved_query_form'; +import { AddToCaseButton } from '../../cases/add_to_cases_button'; export interface LiveQueryFormFields { query?: string; @@ -105,7 +107,7 @@ interface LiveQueryFormProps { formType?: FormType; enabled?: boolean; hideAgentsField?: boolean; - addToTimeline?: (payload: { query: [string, string]; isIcon?: true }) => React.ReactElement; + addToTimeline?: (payload: AddToTimelinePayload) => React.ReactElement; } const LiveQueryFormComponent: React.FC = ({ @@ -278,6 +280,26 @@ const LiveQueryFormComponent: React.FC = ({ ); const singleQueryDetails = useMemo(() => liveQueryDetails?.queries?.[0], [liveQueryDetails]); + const liveQueryActionId = useMemo(() => liveQueryDetails?.action_id, [liveQueryDetails]); + + const addToCaseButton = useCallback( + (payload) => { + if (liveQueryActionId) { + return ( + + ); + } + + return <>; + }, + [agentIds, liveQueryActionId] + ); const resultsStepContent = useMemo( () => @@ -288,6 +310,7 @@ const LiveQueryFormComponent: React.FC = ({ endDate={singleQueryDetails?.expiration} agentIds={singleQueryDetails?.agents} addToTimeline={addToTimeline} + addToCase={addToCaseButton} /> ) : null, [ @@ -296,6 +319,7 @@ const LiveQueryFormComponent: React.FC = ({ singleQueryDetails?.agents, serializedData.ecs_mapping, addToTimeline, + addToCaseButton, ] ); @@ -459,7 +483,9 @@ const LiveQueryFormComponent: React.FC = ({ agentIds={agentIds} // @ts-expect-error version string !+ string[] data={liveQueryDetails?.queries ?? selectedPackData?.attributes?.queries} + addToCase={addToCaseButton} addToTimeline={addToTimeline} + showResultsHeader /> diff --git a/x-pack/plugins/osquery/public/live_queries/form/pack_queries_status_table.tsx b/x-pack/plugins/osquery/public/live_queries/form/pack_queries_status_table.tsx index 9ba830e96311a..38cd6d9cedc3c 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/pack_queries_status_table.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/pack_queries_status_table.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { get } from 'lodash'; +import { get, map } from 'lodash'; import type { ReactElement } from 'react'; import React, { useCallback, useEffect, useState, useMemo } from 'react'; import { @@ -34,6 +34,9 @@ import type { import { DOCUMENT_FIELD_NAME as RECORDS_FIELD } from '@kbn/lens-plugin/common/constants'; import { FilterStateStore } from '@kbn/es-query'; import styled from 'styled-components'; +import { SECURITY_APP_NAME } from '../../timelines/get_add_to_timeline'; +import type { AddToTimelinePayload } from '../../timelines/get_add_to_timeline'; +import { PackResultsHeader } from './pack_results_header'; import { Direction } from '../../../common/search_strategy'; import { removeMultilines } from '../../../common/utils/build_query/remove_multilines'; import { useKibana } from '../../common/lib/kibana'; @@ -43,6 +46,8 @@ import type { PackItem } from '../../packs/types'; import type { LogsDataView } from '../../common/hooks/use_logs_data_view'; import { useLogsDataView } from '../../common/hooks/use_logs_data_view'; +const CASES_OWNER: string[] = []; + const TruncateTooltipText = styled.div` width: 100%; @@ -526,22 +531,39 @@ type PackQueryStatusItem = Partial<{ interface PackQueriesStatusTableProps { agentIds?: string[]; + queryId?: string; actionId?: string; data?: PackQueryStatusItem[]; startDate?: string; expirationDate?: string; - addToTimeline?: (payload: { query: [string, string]; isIcon?: true }) => ReactElement; + addToTimeline?: (payload: AddToTimelinePayload) => ReactElement; + addToCase?: ({ + actionId, + isIcon, + isDisabled, + }: { + actionId?: string; + isIcon?: boolean; + isDisabled?: boolean; + }) => ReactElement; + showResultsHeader?: boolean; } const PackQueriesStatusTableComponent: React.FC = ({ actionId, + queryId, agentIds, data, startDate, expirationDate, addToTimeline, + addToCase, + showResultsHeader, }) => { const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>({}); + const { cases, timelines, appName } = useKibana().services; + const casePermissions = cases.helpers.canUseCases(); + const CasesContext = cases.ui.getCasesContext(); const renderIDColumn = useCallback( (id: string) => ( @@ -595,7 +617,18 @@ const PackQueriesStatusTableComponent: React.FC = ( ); const renderLensResultsAction = useCallback((item) => , []); + const handleAddToCase = useCallback( + (payload: { actionId: string; isIcon?: boolean }) => + // eslint-disable-next-line react/display-name + () => { + if (addToCase) { + return addToCase({ actionId: payload.actionId, isIcon: payload?.isIcon }); + } + return <>; + }, + [addToCase] + ); const getHandleErrorsToggle = useCallback( (item) => () => { setItemIdToExpandedRowMap((prevValue) => { @@ -614,6 +647,7 @@ const PackQueriesStatusTableComponent: React.FC = ( agentIds={agentIds} failedAgentsCount={item?.failed ?? 0} addToTimeline={addToTimeline} + addToCase={addToCase && handleAddToCase({ actionId: item.action_id })} /> @@ -623,7 +657,7 @@ const PackQueriesStatusTableComponent: React.FC = ( return itemIdToExpandedRowMapValues; }); }, - [agentIds, expirationDate, startDate, addToTimeline] + [startDate, expirationDate, agentIds, addToTimeline, addToCase, handleAddToCase] ); const renderToggleResultsAction = useCallback( @@ -642,8 +676,28 @@ const PackQueriesStatusTableComponent: React.FC = ( const getItemId = useCallback((item: PackItem) => get(item, 'id'), []); - const columns = useMemo( - () => [ + const columns = useMemo(() => { + const resultActions = [ + { + render: renderDiscoverResultsAction, + }, + { + render: renderLensResultsAction, + }, + { + available: () => !!addToCase, + render: (item: { action_id: string }) => + addToCase && + addToCase({ actionId: item.action_id, isIcon: true, isDisabled: !item.action_id }), + }, + { + available: () => addToTimeline && timelines && appName === SECURITY_APP_NAME, + render: (item: { action_id: string }) => + addToTimeline && addToTimeline({ query: ['action_id', item.action_id], isIcon: true }), + }, + ]; + + return [ { field: 'id', name: i18n.translate('xpack.osquery.pack.queriesTable.idColumnTitle', { @@ -679,14 +733,7 @@ const PackQueriesStatusTableComponent: React.FC = ( defaultMessage: 'View results', }), width: '90px', - actions: [ - { - render: renderDiscoverResultsAction, - }, - { - render: renderLensResultsAction, - }, - ], + actions: resultActions, }, { id: 'actions', @@ -699,17 +746,20 @@ const PackQueriesStatusTableComponent: React.FC = ( }, ], }, - ], - [ - renderIDColumn, - renderQueryColumn, - renderDocsColumn, - renderAgentsColumn, - renderDiscoverResultsAction, - renderLensResultsAction, - renderToggleResultsAction, - ] - ); + ]; + }, [ + renderDiscoverResultsAction, + renderLensResultsAction, + renderIDColumn, + renderQueryColumn, + renderDocsColumn, + renderAgentsColumn, + renderToggleResultsAction, + addToCase, + addToTimeline, + timelines, + appName, + ]); const sorting = useMemo( () => ({ @@ -724,7 +774,7 @@ const PackQueriesStatusTableComponent: React.FC = ( useEffect(() => { // reset the expanded row map when the data changes setItemIdToExpandedRowMap({}); - }, [actionId]); + }, [queryId, actionId]); useEffect(() => { if ( @@ -737,15 +787,30 @@ const PackQueriesStatusTableComponent: React.FC = ( } }, [agentIds?.length, data, getHandleErrorsToggle, itemIdToExpandedRowMap]); + const queryIds = useMemo( + () => + map(data, (query) => ({ + value: query.action_id || '', + field: 'action_id', + })), + [data] + ); + return ( - + + {showResultsHeader && ( + + )} + + + ); }; diff --git a/x-pack/plugins/osquery/public/live_queries/form/pack_results_header.tsx b/x-pack/plugins/osquery/public/live_queries/form/pack_results_header.tsx new file mode 100644 index 0000000000000..854548fdb58e4 --- /dev/null +++ b/x-pack/plugins/osquery/public/live_queries/form/pack_results_header.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { ReactElement } from 'react'; +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import styled from 'styled-components'; + +interface PackResultsHeadersProps { + actionId?: string; + addToCase?: ({ + isIcon, + iconProps, + }: { + isIcon: boolean; + iconProps: Record; + }) => ReactElement; + queryIds: Array<{ value: string; field: string }>; +} + +const StyledResultsHeading = styled(EuiFlexItem)` + padding-right: 20px; + border-right: 2px solid #d3dae6; +`; + +const StyledIconsList = styled(EuiFlexItem)` + align-content: center; + justify-content: center; + padding-left: 10px; +`; + +export const PackResultsHeader = ({ actionId, addToCase }: PackResultsHeadersProps) => ( + <> + + + +

+ +

+
+
+ + + {actionId && + addToCase && + addToCase({ + isIcon: true, + iconProps: { + color: 'text', + size: 'xs', + iconSize: 'l', + }, + })} + + +
+ + +); diff --git a/x-pack/plugins/osquery/public/live_queries/index.tsx b/x-pack/plugins/osquery/public/live_queries/index.tsx index 746687f4b644b..ba953b4aad6da 100644 --- a/x-pack/plugins/osquery/public/live_queries/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/index.tsx @@ -10,6 +10,7 @@ import { EuiCode, EuiLoadingContent, EuiEmptyPrompt } from '@elastic/eui'; import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; +import type { AddToTimelinePayload } from '../timelines/get_add_to_timeline'; import type { EcsMappingSerialized } from '../packs/queries/ecs_mapping_editor_field'; import { LiveQueryForm } from './form'; import { useActionResultsPrivileges } from '../action_results/use_action_privileges'; @@ -33,7 +34,7 @@ interface LiveQueryProps { hideAgentsField?: boolean; packId?: string; agentSelection?: AgentSelection; - addToTimeline?: (payload: { query: [string, string]; isIcon?: true }) => React.ReactElement; + addToTimeline?: (payload: AddToTimelinePayload) => React.ReactElement; } const LiveQueryComponent: React.FC = ({ diff --git a/x-pack/plugins/osquery/public/plugin.ts b/x-pack/plugins/osquery/public/plugin.ts index ddea34a936178..64713f16699f2 100644 --- a/x-pack/plugins/osquery/public/plugin.ts +++ b/x-pack/plugins/osquery/public/plugin.ts @@ -19,6 +19,7 @@ import type { OsqueryPluginStart, StartPlugins, AppPluginStartDependencies, + SetupPlugins, } from './types'; import { OSQUERY_INTEGRATION_NAME, PLUGIN_NAME } from '../common'; import { @@ -30,7 +31,9 @@ import { getLazyOsqueryAction, getLazyLiveQueryField, useIsOsqueryAvailableSimple, + getExternalReferenceAttachmentRegular, } from './shared_components'; +import type { ServicesWrapperProps } from './shared_components/services_wrapper'; export class OsqueryPlugin implements Plugin { private kibanaVersion: string; @@ -40,7 +43,7 @@ export class OsqueryPlugin implements Plugin { + plugins.cases?.attachmentFramework.registerExternalReference( + getExternalReferenceAttachmentRegular({ + ...coreStart, + ...depsStart, + storage, + kibanaVersion, + } as unknown as ServicesWrapperProps['services']) + ); + }); + // Return methods that should be available to other plugins return {}; } diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx index a1658538045a6..2d8822b9ab4a8 100644 --- a/x-pack/plugins/osquery/public/results/results_table.tsx +++ b/x-pack/plugins/osquery/public/results/results_table.tsx @@ -28,6 +28,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react'; import { pagePathGetters } from '@kbn/fleet-plugin/public'; +import type { AddToTimelinePayload } from '../timelines/get_add_to_timeline'; import type { ECSMapping } from '../../common/schemas/common'; import { useAllResults } from './use_all_results'; import type { ResultEdges } from '../../common/search_strategy'; @@ -52,7 +53,8 @@ interface ResultsTableComponentProps { ecsMapping?: ECSMapping; endDate?: string; startDate?: string; - addToTimeline?: (payload: { query: [string, string]; isIcon?: true }) => React.ReactElement; + addToTimeline?: (payload: AddToTimelinePayload) => React.ReactElement; + addToCase?: ({ actionId }: { actionId?: string }) => React.ReactElement; } const ResultsTableComponent: React.FC = ({ @@ -62,6 +64,7 @@ const ResultsTableComponent: React.FC = ({ startDate, endDate, addToTimeline, + addToCase, }) => { const [isLive, setIsLive] = useState(true); const { data: hasActionResultsPrivileges } = useActionResultsPrivileges(); @@ -343,10 +346,11 @@ const ResultsTableComponent: React.FC = ({ startDate={startDate} /> {addToTimeline && addToTimeline({ query: ['action_id', actionId] })} + {addToCase && addToCase({ actionId })} ), }), - [actionId, addToTimeline, endDate, startDate] + [actionId, addToCase, addToTimeline, endDate, startDate] ); useEffect( diff --git a/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx b/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx index bcc069da7359f..a4c58074c76e1 100644 --- a/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx +++ b/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx @@ -7,9 +7,10 @@ import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useLayoutEffect, useMemo, useState } from 'react'; +import React, { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; +import { AddToCaseButton } from '../../../cases/add_to_cases_button'; import { useRouterNavigate } from '../../../common/lib/kibana'; import { WithHeaderLayout } from '../../../components/layouts'; import { useLiveQueryDetails } from '../../../actions/use_live_query_details'; @@ -52,6 +53,19 @@ const LiveQueryDetailsPageComponent = () => { useLayoutEffect(() => { setIsLive(() => !(data?.status === 'completed')); }, [data?.status]); + const addToCaseButton = useCallback( + (payload) => ( + + ), + [data?.agents, actionId] + ); return ( @@ -61,6 +75,8 @@ const LiveQueryDetailsPageComponent = () => { startDate={data?.['@timestamp']} expirationDate={data?.expiration} agentIds={data?.agents} + addToCase={addToCaseButton} + showResultsHeader /> ); diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx index 081530479c8b1..98731a67ee837 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx @@ -8,11 +8,15 @@ import { EuiTabbedContent, EuiNotificationBadge } from '@elastic/eui'; import React, { useMemo } from 'react'; import type { ReactElement } from 'react'; +import { useKibana } from '../../../common/lib/kibana'; +import type { AddToTimelinePayload } from '../../../timelines/get_add_to_timeline'; import type { ECSMapping } from '../../../../common/schemas/common'; import { ResultsTable } from '../../../results/results_table'; import { ActionResultsSummary } from '../../../action_results/action_results_summary'; +const CASES_OWNER: string[] = []; + interface ResultTabsProps { actionId: string; agentIds?: string[]; @@ -20,7 +24,8 @@ interface ResultTabsProps { ecsMapping?: ECSMapping; failedAgentsCount?: number; endDate?: string; - addToTimeline?: (payload: { query: [string, string]; isIcon?: true }) => ReactElement; + addToTimeline?: (payload: AddToTimelinePayload) => ReactElement; + addToCase?: ({ actionId }: { actionId?: string }) => ReactElement; } const ResultTabsComponent: React.FC = ({ @@ -31,7 +36,12 @@ const ResultTabsComponent: React.FC = ({ failedAgentsCount, startDate, addToTimeline, + addToCase, }) => { + const { cases } = useKibana().services; + const casePermissions = cases.helpers.canUseCases(); + const CasesContext = cases.ui.getCasesContext(); + const tabs = useMemo( () => [ { @@ -45,6 +55,7 @@ const ResultTabsComponent: React.FC = ({ startDate={startDate} endDate={endDate} addToTimeline={addToTimeline} + addToCase={addToCase} /> ), }, @@ -61,18 +72,29 @@ const ResultTabsComponent: React.FC = ({ ) : null, }, ], - [actionId, agentIds, ecsMapping, startDate, endDate, addToTimeline, failedAgentsCount] + [ + actionId, + agentIds, + ecsMapping, + startDate, + endDate, + addToTimeline, + addToCase, + failedAgentsCount, + ] ); return ( - + + + ); }; diff --git a/x-pack/plugins/osquery/public/shared_components/attachments/external_reference.tsx b/x-pack/plugins/osquery/public/shared_components/attachments/external_reference.tsx new file mode 100644 index 0000000000000..51bfc6ab1c22f --- /dev/null +++ b/x-pack/plugins/osquery/public/shared_components/attachments/external_reference.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiAvatar } from '@elastic/eui'; +import React from 'react'; +import type { ExternalReferenceAttachmentType } from '@kbn/cases-plugin/public/client/attachment_framework/types'; +import { getLazyExternalContent } from './lazy_external_reference_content'; +import type { ServicesWrapperProps } from '../services_wrapper'; +import OsqueryLogo from '../../components/osquery_icon/osquery.svg'; + +export const getExternalReferenceAttachmentRegular = ( + services: ServicesWrapperProps['services'] +): ExternalReferenceAttachmentType => ({ + id: 'osquery', + displayName: 'Osquery', + getAttachmentViewObject: () => ({ + type: 'regular', + event: 'attached Osquery results', + timelineAvatar: , + // @ts-expect-error update types + children: getLazyExternalContent(services), + }), +}); diff --git a/x-pack/plugins/osquery/public/shared_components/attachments/external_references_content.tsx b/x-pack/plugins/osquery/public/shared_components/attachments/external_references_content.tsx new file mode 100644 index 0000000000000..304167a12f57e --- /dev/null +++ b/x-pack/plugins/osquery/public/shared_components/attachments/external_references_content.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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import type { IExternalReferenceMetaDataProps } from './lazy_external_reference_content'; +import { PackQueriesAttachmentWrapper } from './pack_queries_attachment_wrapper'; + +const AttachmentContent = (props: IExternalReferenceMetaDataProps) => { + const { externalReferenceMetadata } = props; + + return ( + + + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { AttachmentContent as default }; diff --git a/x-pack/plugins/osquery/public/shared_components/attachments/lazy_external_reference_content.tsx b/x-pack/plugins/osquery/public/shared_components/attachments/lazy_external_reference_content.tsx new file mode 100644 index 0000000000000..94e4180f30676 --- /dev/null +++ b/x-pack/plugins/osquery/public/shared_components/attachments/lazy_external_reference_content.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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, { lazy, Suspense } from 'react'; +import { EuiCode, EuiEmptyPrompt } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { OsqueryIcon } from '../../components/osquery_icon'; +import { useKibana } from '../../common/lib/kibana'; +import type { ServicesWrapperProps } from '../services_wrapper'; +import ServicesWrapper from '../services_wrapper'; +import { PERMISSION_DENIED } from '../osquery_action/translations'; + +export interface IExternalReferenceMetaDataProps { + externalReferenceMetadata: { + actionId: string; + agentIds: string[]; + queryId: string; + }; +} + +export const getLazyExternalContent = + // eslint-disable-next-line react/display-name + (services: ServicesWrapperProps['services']) => (props: IExternalReferenceMetaDataProps) => { + const { + services: { + application: { + capabilities: { osquery }, + }, + }, + } = useKibana(); + + if (!osquery.read) { + return ( + } + title={

{PERMISSION_DENIED}

} + titleSize="xs" + body={ + osquery, + }} + /> + } + /> + ); + } + + const AttachmentContent = lazy(() => import('./external_references_content')); + + return ( + + + + + + ); + }; diff --git a/x-pack/plugins/osquery/public/shared_components/attachments/pack_queries_attachment_wrapper.tsx b/x-pack/plugins/osquery/public/shared_components/attachments/pack_queries_attachment_wrapper.tsx new file mode 100644 index 0000000000000..c7344f49b32c5 --- /dev/null +++ b/x-pack/plugins/osquery/public/shared_components/attachments/pack_queries_attachment_wrapper.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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, useLayoutEffect, useState } from 'react'; +import { useKibana } from '../../common/lib/kibana'; +import { getAddToTimeline } from '../../timelines/get_add_to_timeline'; +import { PackQueriesStatusTable } from '../../live_queries/form/pack_queries_status_table'; +import { useLiveQueryDetails } from '../../actions/use_live_query_details'; + +interface PackQueriesAttachmentWrapperProps { + actionId?: string; + agentIds: string[]; + queryId: string; +} + +export const PackQueriesAttachmentWrapper = ({ + actionId, + agentIds, + queryId, +}: PackQueriesAttachmentWrapperProps) => { + const { + services: { timelines, appName }, + } = useKibana(); + const [isLive, setIsLive] = useState(false); + const addToTimelineButton = getAddToTimeline(timelines, appName); + + const { data } = useLiveQueryDetails({ + actionId, + isLive, + ...(queryId ? { queryIds: [queryId] } : {}), + }); + + useLayoutEffect(() => { + setIsLive(() => !(data?.status === 'completed')); + }, [data?.status]); + + const addToTimeline = useCallback( + (payload) => { + if (!actionId || !addToTimelineButton) { + return <>; + } + + return addToTimelineButton(payload); + }, + [actionId, addToTimelineButton] + ); + + return ( + + ); +}; diff --git a/x-pack/plugins/osquery/public/shared_components/index.tsx b/x-pack/plugins/osquery/public/shared_components/index.tsx index fee2466a49430..cb4d6881cef71 100644 --- a/x-pack/plugins/osquery/public/shared_components/index.tsx +++ b/x-pack/plugins/osquery/public/shared_components/index.tsx @@ -8,3 +8,4 @@ export { getLazyOsqueryAction } from './lazy_osquery_action'; export { getLazyLiveQueryField } from './lazy_live_query_field'; export { useIsOsqueryAvailableSimple } from './osquery_action/use_is_osquery_available_simple'; +export { getExternalReferenceAttachmentRegular } from './attachments/external_reference'; diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx index bc039b334a910..a3586d6c8c937 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx @@ -8,6 +8,7 @@ import { EuiLoadingContent, EuiEmptyPrompt, EuiCode } from '@elastic/eui'; import React, { useMemo } from 'react'; +import type { AddToTimelinePayload } from '../../timelines/get_add_to_timeline'; import { AGENT_STATUS_ERROR, EMPTY_PROMPT, @@ -25,7 +26,7 @@ export interface OsqueryActionProps { defaultValues?: {}; formType: 'steps' | 'simple'; hideAgentsField?: boolean; - addToTimeline?: (payload: { query: [string, string]; isIcon?: true }) => React.ReactElement; + addToTimeline?: (payload: AddToTimelinePayload) => React.ReactElement; } const OsqueryActionComponent: React.FC = ({ diff --git a/x-pack/plugins/osquery/public/timelines/get_add_to_timeline.tsx b/x-pack/plugins/osquery/public/timelines/get_add_to_timeline.tsx new file mode 100644 index 0000000000000..47cfc34679b33 --- /dev/null +++ b/x-pack/plugins/osquery/public/timelines/get_add_to_timeline.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 React from 'react'; +import { EuiButtonEmpty } from '@elastic/eui'; +import type { ServicesWrapperProps } from '../shared_components/services_wrapper'; + +const TimelineComponent = React.memo((props) => ); +TimelineComponent.displayName = 'TimelineComponent'; + +export interface AddToTimelinePayload { + query: [string, string]; + isIcon?: true; +} + +export const SECURITY_APP_NAME = 'Security'; +export const getAddToTimeline = ( + timelines: ServicesWrapperProps['services']['timelines'], + appName?: string +) => { + if (!timelines || appName !== SECURITY_APP_NAME) { + return; + } + + const { getAddToTimelineButton } = timelines.getHoverActions(); + + return (payload: AddToTimelinePayload) => { + const { + query: [field, value], + isIcon, + } = payload; + + const providerA = { + and: [], + enabled: true, + excluded: false, + id: value, + kqlQuery: '', + name: value, + queryMatch: { + field, + value, + operator: ':' as const, + }, + }; + + return getAddToTimelineButton({ + dataProvider: providerA, + field: value, + ownFocus: false, + ...(isIcon ? { showTooltip: true } : { Component: TimelineComponent }), + }); + }; +}; diff --git a/x-pack/plugins/osquery/public/types.ts b/x-pack/plugins/osquery/public/types.ts index c19dd10802f32..30207b51c4c85 100644 --- a/x-pack/plugins/osquery/public/types.ts +++ b/x-pack/plugins/osquery/public/types.ts @@ -16,10 +16,13 @@ import type { TriggersAndActionsUIPublicPluginSetup, TriggersAndActionsUIPublicPluginStart, } from '@kbn/triggers-actions-ui-plugin/public'; +import type { CasesUiStart, CasesUiSetup } from '@kbn/cases-plugin/public'; +import type { TimelinesUIStart } from '@kbn/timelines-plugin/public'; import type { getLazyLiveQueryField, getLazyOsqueryAction } from './shared_components'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface OsqueryPluginSetup {} + export interface OsqueryPluginStart { OsqueryAction?: ReturnType; LiveQueryField?: ReturnType; @@ -37,10 +40,14 @@ export interface StartPlugins { lens?: LensPublicStart; security: SecurityPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; + cases: CasesUiStart; + timelines: TimelinesUIStart; + appName?: string; } export interface SetupPlugins { triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; + cases?: CasesUiSetup; } export type StartServices = CoreStart & StartPlugins; diff --git a/x-pack/plugins/osquery/scripts/roles_users/soc_manager/role.json b/x-pack/plugins/osquery/scripts/roles_users/soc_manager/role.json index e427fece0ea01..2c029fe75d3fb 100644 --- a/x-pack/plugins/osquery/scripts/roles_users/soc_manager/role.json +++ b/x-pack/plugins/osquery/scripts/roles_users/soc_manager/role.json @@ -17,6 +17,8 @@ "feature": { "discover": ["read"], "infrastructure": ["read"], + "observabilityCases": ["all"], + "securitySolutionCases": ["all"], "ml": ["all"], "siem": ["all"], "osquery": ["all"], diff --git a/x-pack/plugins/osquery/server/utils/register_features.ts b/x-pack/plugins/osquery/server/utils/register_features.ts index 9627d2a179232..4925b1ea14ad6 100644 --- a/x-pack/plugins/osquery/server/utils/register_features.ts +++ b/x-pack/plugins/osquery/server/utils/register_features.ts @@ -34,7 +34,7 @@ export const registerFeatures = (features: SetupPlugins['features']) => { all: [], read: [], }, - ui: ['write'], + ui: ['read', 'write'], }, read: { api: [`${PLUGIN_ID}-read`], diff --git a/x-pack/plugins/osquery/tsconfig.json b/x-pack/plugins/osquery/tsconfig.json index f9cacf100b691..37070a7af748d 100644 --- a/x-pack/plugins/osquery/tsconfig.json +++ b/x-pack/plugins/osquery/tsconfig.json @@ -30,6 +30,7 @@ // optionalPlugins from ./kibana.json { "path": "../../../src/plugins/home/tsconfig.json" }, + { "path": "../cases/tsconfig.json" }, // requiredBundles from ./kibana.json { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" }, diff --git a/x-pack/plugins/profiling/.gitignore b/x-pack/plugins/profiling/.gitignore new file mode 100644 index 0000000000000..eb5a316cbd195 --- /dev/null +++ b/x-pack/plugins/profiling/.gitignore @@ -0,0 +1 @@ +target diff --git a/x-pack/plugins/profiling/.i18nrc.json b/x-pack/plugins/profiling/.i18nrc.json new file mode 100644 index 0000000000000..de8ac3249413e --- /dev/null +++ b/x-pack/plugins/profiling/.i18nrc.json @@ -0,0 +1,7 @@ +{ + "prefix": "profiling", + "paths": { + "profiling": "." + }, + "translations": [] +} diff --git a/x-pack/plugins/profiling/DOCKER.md b/x-pack/plugins/profiling/DOCKER.md new file mode 100644 index 0000000000000..632c6b7a38c07 --- /dev/null +++ b/x-pack/plugins/profiling/DOCKER.md @@ -0,0 +1,68 @@ +# Containerized development environment for Kibana + +Kibana development moves quickly and has specific NodeJS requirements, which can +change depending on the current branch or commit. This makes developing a plugin +challenging for local development. + +We created a containerized environment to encapsulate the necessary dependencies +and reduce issues, especially when switching between Kibana versions. + +## Assumptions + +By default, this setup assumes that you are running Elasticsearch via the +`apm-integration-testing` repo. However, you can override this as described +below. + +## Usage + +You will first need to build the Docker image for the Kibana environment: + +``` +make build +``` + +This will create a Docker image with the tag `kibana-dev:latest`. + +If you wish to change the image version, you can run this instead: + +``` +make build KIBANA_VERSION=8.4 +``` + +If you need to do a full refresh of the Docker image, you can rebuild from +scratch using this: + +``` +make build-nocache +``` + +Next, you can start the container using this (assumes Docker image is +`kibana-dev:latest` and Elasticsearch is running on the `apm-integration-testing` +Docker network): + +``` +make run +``` + +If your Elasticsearch instance is not running via Docker, you can run this: + +``` +make run-networkless +``` + +## Configuration + +* `KIBANA_VERSION` + +This is the version of the Docker image. This can be used to build separate +images for different Kibana versions. + +* `NETWORK` + +This is the Docker network to use so that Kibana can connect to a local running +instance of Elasticsearch. + +* `PORT` + +This is the exposed port for the Kibana instance. This is useful if you have +conflicts with another running instance. diff --git a/x-pack/plugins/profiling/Dockerfile b/x-pack/plugins/profiling/Dockerfile new file mode 100644 index 0000000000000..777a7ba9cce28 --- /dev/null +++ b/x-pack/plugins/profiling/Dockerfile @@ -0,0 +1,48 @@ +FROM ubuntu:22.04 + +RUN rm /bin/sh && ln -s /bin/bash /bin/sh + +# Install base dependencies +RUN apt-get update + +RUN DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata + +RUN apt-get install -y -q --no-install-recommends \ + apt-transport-https \ + build-essential \ + ca-certificates \ + curl \ + git \ + libssl-dev \ + python3 \ + wget \ + && rm -rf /var/lib/apt/lists/* + +RUN useradd -ms /bin/bash -d /kibana kibana + +# Install and setup nvm and node +USER kibana + +ARG NODE_VERSION + +ENV NVM_VERSION v0.39.1 +ENV NVM_DIR /kibana/nvm + +RUN mkdir $NVM_DIR \ + && cd $NVM_DIR \ + && curl https://raw.githubusercontent.com/nvm-sh/nvm/$NVM_VERSION/install.sh | bash \ + && . ./nvm.sh \ + && nvm install $NODE_VERSION \ + && nvm alias default $NODE_VERSION \ + && nvm use default + +ENV NODE_PATH $NVM_DIR/versions/node/v$NODE_VERSION/lib/node_modules +ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH + +# Install yarn +USER kibana +WORKDIR /kibana +RUN npm install -g yarn + +WORKDIR /kibana/src +CMD ["bash"] diff --git a/x-pack/plugins/profiling/INTERNALS.md b/x-pack/plugins/profiling/INTERNALS.md new file mode 100644 index 0000000000000..9612a64380efd --- /dev/null +++ b/x-pack/plugins/profiling/INTERNALS.md @@ -0,0 +1,97 @@ +This plugin is divided into two components: the UI and the server. + +The UI is responsible for rendering the charts and flamegraphs. It will make +API calls to the server component depending on the user interactions with the +UI. You will find most of the source code in the `public` directory. + +The server is responsible for retrieving data from the necessary sources and +sending it in the expected format to the UI in response to the UI's API calls. +You will find most of the source code in the `server` directory. + +## Server API + +Depending on how the plugin is configured, there are two different sets of API +calls. + +If the server uses local fixtures as a data source, then the following API is +served by the server and called by the UI: + +* /api/prodfiler/v1/topn/containers +* /api/prodfiler/v1/topn/deployments +* /api/prodfiler/v1/topn/hosts +* /api/prodfiler/v1/topn/threads +* /api/prodfiler/v1/topn/traces +* /api/prodfiler/v1/flamechart/elastic + +If the server uses an Elasticsearch cluster as a data source, then the following +API is served by the server and called by the UI: + +* /api/prodfiler/v2/topn/containers +* /api/prodfiler/v2/topn/deployments +* /api/prodfiler/v2/topn/hosts +* /api/prodfiler/v2/topn/threads +* /api/prodfiler/v2/topn/traces +* /api/prodfiler/v2/flamechart/elastic + +By default, the plugin is configured to use the second API set. See README.md to +configure the plugin to use the first API set (aka local fixtures as a data +source). + +Both API sets are expected to return the same response format. + +The design to have separate API sets for local vs Elasticsearch was partly +because the UI and server components were originally developed separately and +later merged. However, it also allows the server methods to have a single +responsibility, making it easier to test and verify that the server returns +the expected responses for the given data sources. + +## Server API Responses + +### /api/prodfiler/*/flamechart/elastic + +The response returned from this API is used by the Elastic flamegraph. + +The following example is the expected response: + +```json +{ + leaves: [ + { + id: 'pf-collection-agent: runtime.releaseSudog() in runtime2.go#282', + value: 1, + depth: 19, + pathFromRoot: { + '0': 'root', + '1': 'pf-collection-agent: runtime.goexit() in asm_amd64.s#1581', + '2': 'pf-collection-agent: github.com/optimyze/prodfiler/pf-storage-backend/storagebackend/storagebackendv1.(*ScyllaExecutor).Start.func1 in scyllaexecutor.go#102', + '3': 'pf-collection-agent: github.com/optimyze/prodfiler/pf-storage-backend/storagebackend/storagebackendv1.(*ScyllaExecutor).executeQueryAndReadResults in scyllaexecutor.go#158', + '4': 'pf-collection-agent: github.com/gocql/gocql.(*Query).Iter in session.go#1246', + '5': 'pf-collection-agent: github.com/gocql/gocql.(*Session).executeQuery in session.go#463', + '6': 'pf-collection-agent: github.com/gocql/gocql.(*queryExecutor).executeQuery in query_executor.go#66', + '7': 'pf-collection-agent: github.com/gocql/gocql.(*queryExecutor).do in query_executor.go#127', + '8': 'pf-collection-agent: github.com/gocql/gocql.(*queryExecutor).attemptQuery in query_executor.go#32', + '9': 'pf-collection-agent: github.com/gocql/gocql.(*Query).execute in session.go#1044', + '10': 'pf-collection-agent: github.com/gocql/gocql.(*Conn).executeQuery in conn.go#1129', + '11': 'pf-collection-agent: github.com/gocql/gocql.(*Conn).exec in conn.go#916', + '12': 'pf-collection-agent: github.com/gocql/gocql.(*writeExecuteFrame).writeFrame in frame.go#1618', + '13': 'pf-collection-agent: github.com/gocql/gocql.(*framer).writeExecuteFrame in frame.go#1643', + '14': 'pf-collection-agent: github.com/gocql/gocql.(*framer).finishWrite in frame.go#788', + '15': 'pf-collection-agent: github.com/gocql/gocql.(*Conn).Write in conn.go#319', + '16': 'pf-collection-agent: github.com/gocql/gocql.(*writeCoalescer).Write in conn.go#829', + '17': 'pf-collection-agent: sync.(*Cond).Wait in cond.go#83', + '18': 'pf-collection-agent: sync.runtime_notifyListWait() in sema.go#498', + '19': 'pf-collection-agent: runtime.releaseSudog() in runtime2.go#282', + }, + }, + ... + ] +} +``` + +Here is a basic description of the response format: + +* Each object in the `leaves` list represents a leaf node in the flamegraph +* `id` represents the name of the flamegraph node +* `value` represents the number of samples for that node +* `depth` represents the depth of the node in the flamegraph, starting from zero +* `pathFromRoot` represents the full path from the flamegraph root to the given node diff --git a/x-pack/plugins/profiling/Makefile b/x-pack/plugins/profiling/Makefile new file mode 100644 index 0000000000000..3f624ceea3408 --- /dev/null +++ b/x-pack/plugins/profiling/Makefile @@ -0,0 +1,24 @@ +KIBANA_REPO = $(shell git rev-parse --show-toplevel) +NODE_VERSION = $(shell cat ${KIBANA_REPO}/.node-version) + +KIBANA_VERSION ?= latest +NETWORK ?= apm-integration-testing +PORT ?= 5601 + +DOCKER_IMAGE = kibana-dev:${KIBANA_VERSION} +DOCKER_BUILD_ARGS = --build-arg NODE_VERSION="${NODE_VERSION}" -t ${DOCKER_IMAGE} . +DOCKER_RUN_ARGS = --rm -p ${PORT}:5601 -p 9229-9231:9229-9231/tcp -v ${KIBANA_REPO}:/kibana/src -it ${DOCKER_IMAGE} + +.PHONY: build build-nocache run run-networkless + +build: + docker build ${DOCKER_BUILD_ARGS} + +build-nocache: + docker build --no-cache ${DOCKER_BUILD_ARGS} + +run: + docker run --network ${NETWORK} ${DOCKER_RUN_ARGS} + +run-networkless: + docker run ${DOCKER_RUN_ARGS} diff --git a/x-pack/plugins/profiling/README.md b/x-pack/plugins/profiling/README.md new file mode 100644 index 0000000000000..4b2108cc6779e --- /dev/null +++ b/x-pack/plugins/profiling/README.md @@ -0,0 +1 @@ +### TODO diff --git a/x-pack/plugins/profiling/common/__fixtures__/stacktraces.ts b/x-pack/plugins/profiling/common/__fixtures__/stacktraces.ts new file mode 100644 index 0000000000000..73b3830595830 --- /dev/null +++ b/x-pack/plugins/profiling/common/__fixtures__/stacktraces.ts @@ -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 { createStackFrameID } from '../profiling'; + +enum stackTraceID { + A = 'yU2Oct2ct0HkxJ7-pRcPkg==', + B = 'Xt8aKN70PDXpMDLCOmojzQ==', + C = '8OauxYq2WK4_tBqM4xkIwA==', + D = 'nQWGdRxvqVjwlLmQWH1Phw==', + E = '2KciEEWALlol3b6x95PHcw==', + F = 'BxRgiXa4h9Id6BjdPPHK8Q==', +} + +enum fileID { + A = 'Ncujji3wC1nL73TTEyFBhA==', + B = 'T2vdys5d7j85az1aP86zCg==', + C = 'jMaTVVjYv7cecd0C4HguGw==', + D = 'RLkjnlfcvSJN2Wph9WUuOQ==', + E = 'gnEsgxvvEODj6iFYMQWYlA==', + F = 'Gf4xoLc8QuAHU49Ch_CFOA==', + G = 'ZCOCZlls7r2cbG1HchkbVg==', + H = 'Og7kGWGe9qiCunkaXDffHQ==', + I = 'WAE6T1TeDsjDMOuwX4Ynxg==', + J = 'ZNiZco1zgh0nJI6hPllMaQ==', + K = 'abl5r8Vvvb2Y7NaDZW1QLQ==', +} + +enum addressOrLine { + A = 26278522, + B = 6712518, + C = 105806025, + D = 105806854, + E = 107025202, + F = 107044169, + G = 18353156, + H = 3027, + I = 5201, + J = 67384048, + K = 8888, +} + +const frameID: Record = { + A: createStackFrameID(fileID.A, addressOrLine.A), + B: createStackFrameID(fileID.B, addressOrLine.B), + C: createStackFrameID(fileID.C, addressOrLine.C), + D: createStackFrameID(fileID.D, addressOrLine.D), + E: createStackFrameID(fileID.E, addressOrLine.C), + F: createStackFrameID(fileID.E, addressOrLine.D), + G: createStackFrameID(fileID.E, addressOrLine.E), + H: createStackFrameID(fileID.E, addressOrLine.F), + I: createStackFrameID(fileID.E, addressOrLine.G), + J: createStackFrameID(fileID.F, addressOrLine.H), + K: createStackFrameID(fileID.F, addressOrLine.I), + L: createStackFrameID(fileID.F, addressOrLine.J), + M: createStackFrameID(fileID.F, addressOrLine.K), + N: createStackFrameID(fileID.G, addressOrLine.G), + O: createStackFrameID(fileID.H, addressOrLine.H), + P: createStackFrameID(fileID.I, addressOrLine.I), + Q: createStackFrameID(fileID.F, addressOrLine.A), + R: createStackFrameID(fileID.E, addressOrLine.B), + S: createStackFrameID(fileID.E, addressOrLine.C), +}; + +export const events = new Map([ + [stackTraceID.A, 16], + [stackTraceID.B, 9], + [stackTraceID.C, 7], + [stackTraceID.D, 5], + [stackTraceID.E, 2], + [stackTraceID.F, 1], +]); + +export const stackTraces = new Map([ + [ + stackTraceID.A, + { + FileIDs: [fileID.D, fileID.C, fileID.B, fileID.A], + AddressOrLines: [addressOrLine.D, addressOrLine.C, addressOrLine.B, addressOrLine.A], + FrameIDs: [frameID.D, frameID.C, frameID.B, frameID.A], + Types: [3, 3, 3, 3], + }, + ], + [ + stackTraceID.B, + { + FileIDs: [fileID.E, fileID.E, fileID.E, fileID.E, fileID.E], + AddressOrLines: [ + addressOrLine.G, + addressOrLine.F, + addressOrLine.E, + addressOrLine.D, + addressOrLine.C, + ], + FrameIDs: [frameID.I, frameID.H, frameID.G, frameID.F, frameID.E], + Types: [3, 3, 3, 3, 3], + }, + ], + [ + stackTraceID.C, + { + FileIDs: [fileID.F, fileID.F, fileID.F, fileID.F], + AddressOrLines: [addressOrLine.K, addressOrLine.J, addressOrLine.I, addressOrLine.H], + FrameIDs: [frameID.M, frameID.L, frameID.K, frameID.J], + Types: [3, 3, 3, 3], + }, + ], + [ + stackTraceID.D, + { + FileIDs: [fileID.I, fileID.H, fileID.G], + AddressOrLines: [addressOrLine.I, addressOrLine.H, addressOrLine.G], + FrameIDs: [frameID.P, frameID.O, frameID.N], + Types: [3, 8, 8], + }, + ], + [ + stackTraceID.E, + { + FileIDs: [fileID.F, fileID.F, fileID.F], + AddressOrLines: [addressOrLine.K, addressOrLine.J, addressOrLine.I], + FrameIDs: [frameID.M, frameID.L, frameID.K], + Types: [3, 3, 3], + }, + ], + [ + stackTraceID.F, + { + FileIDs: [fileID.E, fileID.E], + AddressOrLines: [addressOrLine.F, addressOrLine.E], + FrameIDs: [frameID.H, frameID.G], + Types: [3, 3], + }, + ], +]); + +const defaultStackFrame = { + FileName: '', + FunctionName: '', + FunctionOffset: 0, + LineNumber: 0, + SourceType: 0, +}; + +export const stackFrames = new Map([ + [ + frameID.A, + { + FileName: 'ThreadPoolExecutor.java', + FunctionName: 'java.lang.Runnable java.util.concurrent.ThreadPoolExecutor.getTask()', + FunctionOffset: 26, + LineNumber: 1061, + SourceType: 5, + }, + ], + [ + frameID.B, + { FileName: '', FunctionName: 'sock_sendmsg', FunctionOffset: 0, LineNumber: 0, SourceType: 0 }, + ], + [frameID.C, defaultStackFrame], + [frameID.D, defaultStackFrame], + [frameID.E, defaultStackFrame], + [frameID.F, defaultStackFrame], + [frameID.G, defaultStackFrame], + [frameID.H, defaultStackFrame], + [frameID.I, defaultStackFrame], + [frameID.J, defaultStackFrame], + [frameID.K, defaultStackFrame], + [ + frameID.L, + { FileName: '', FunctionName: 'udp_sendmsg', FunctionOffset: 0, LineNumber: 0, SourceType: 0 }, + ], + [frameID.M, defaultStackFrame], + [frameID.N, defaultStackFrame], + [frameID.O, defaultStackFrame], + [frameID.P, defaultStackFrame], + [frameID.Q, defaultStackFrame], + [frameID.R, defaultStackFrame], + [frameID.S, defaultStackFrame], +]); + +export const executables = new Map([ + [fileID.A, { FileName: '' }], + [fileID.B, { FileName: '' }], + [fileID.C, { FileName: '' }], + [fileID.D, { FileName: 'libglapi.so.0.0.0' }], + [fileID.E, { FileName: '' }], + [fileID.F, { FileName: '' }], + [fileID.G, { FileName: '' }], + [fileID.H, { FileName: '' }], + [fileID.I, { FileName: '' }], +]); diff --git a/x-pack/plugins/profiling/common/callercallee.test.ts b/x-pack/plugins/profiling/common/callercallee.test.ts new file mode 100644 index 0000000000000..af07da439a3e7 --- /dev/null +++ b/x-pack/plugins/profiling/common/callercallee.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 { sum } from 'lodash'; +import { + createCallerCalleeDiagram, + createCallerCalleeIntermediateNode, + fromCallerCalleeIntermediateNode, +} from './callercallee'; +import { createFrameGroupID } from './frame_group'; +import { createStackFrameMetadata } from './profiling'; + +import { events, stackTraces, stackFrames, executables } from './__fixtures__/stacktraces'; + +describe('Caller-callee operations', () => { + test('1', () => { + const parentFrame = createStackFrameMetadata({ + FileID: '6bc50d345244d5956f93a1b88f41874d', + FrameType: 3, + AddressOrLine: 971740, + FunctionName: 'epoll_wait', + SourceID: 'd670b496cafcaea431a23710fb5e4f58', + SourceLine: 30, + ExeFileName: 'libc-2.26.so', + }); + const parent = createCallerCalleeIntermediateNode(parentFrame, 10, 'parent'); + + const childFrame = createStackFrameMetadata({ + FileID: '8d8696a4fd51fa88da70d3fde138247d', + FrameType: 3, + AddressOrLine: 67000, + FunctionName: 'epoll_poll', + SourceID: 'f0a7901dcefed6cc8992a324b9df733c', + SourceLine: 150, + ExeFileName: 'auditd', + }); + const child = createCallerCalleeIntermediateNode(childFrame, 10, 'child'); + + const root = createCallerCalleeIntermediateNode(createStackFrameMetadata(), 10, 'root'); + root.callees.set(createFrameGroupID(child.frameGroup), child); + root.callees.set(createFrameGroupID(parent.frameGroup), parent); + + const graph = fromCallerCalleeIntermediateNode(root); + + // Modify original frames to verify graph does not contain references + parent.samples = 30; + child.samples = 20; + + expect(graph.Callees[0].Samples).toEqual(10); + expect(graph.Callees[1].Samples).toEqual(10); + }); + + test('2', () => { + const totalSamples = sum([...events.values()]); + + const root = createCallerCalleeDiagram(events, stackTraces, stackFrames, executables); + expect(root.Samples).toEqual(totalSamples); + expect(root.CountInclusive).toEqual(totalSamples); + expect(root.CountExclusive).toEqual(0); + }); +}); diff --git a/x-pack/plugins/profiling/common/callercallee.ts b/x-pack/plugins/profiling/common/callercallee.ts new file mode 100644 index 0000000000000..e4472968c4d39 --- /dev/null +++ b/x-pack/plugins/profiling/common/callercallee.ts @@ -0,0 +1,322 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { clone } from 'lodash'; +import { + compareFrameGroup, + createFrameGroup, + createFrameGroupID, + FrameGroup, + FrameGroupID, +} from './frame_group'; +import { + createStackFrameMetadata, + Executable, + FileID, + groupStackFrameMetadataByStackTrace, + StackFrame, + StackFrameID, + StackFrameMetadata, + StackTrace, + StackTraceID, +} from './profiling'; + +export interface CallerCalleeIntermediateNode { + frameGroup: FrameGroup; + frameGroupID: string; + callers: Map; + callees: Map; + frameMetadata: Set; + samples: number; + countInclusive: number; + countExclusive: number; +} + +export function createCallerCalleeIntermediateNode( + frameMetadata: StackFrameMetadata, + samples: number, + frameGroupID: string +): CallerCalleeIntermediateNode { + return { + frameGroup: createFrameGroup(frameMetadata), + callers: new Map(), + callees: new Map(), + frameMetadata: new Set([frameMetadata]), + samples, + countInclusive: 0, + countExclusive: 0, + frameGroupID, + }; +} + +interface RelevantTrace { + frames: StackFrameMetadata[]; + index: number; +} + +// selectRelevantTraces searches through a map that maps trace hashes to their +// frames and only returns those traces that have a frame that are equivalent +// to the rootFrame provided. It also sets the "index" in the sequence of +// traces at which the rootFrame is found. +// +// If the rootFrame is "empty" (e.g. fileID is empty and line number is 0), all +// traces in the given time frame are deemed relevant, and the "index" is set +// to the length of the trace -- since there is no root frame, the frame should +// be considered "calls-to" only going. +function selectRelevantTraces( + rootFrame: StackFrameMetadata, + frames: Map +): Map { + const result = new Map(); + const rootString = createFrameGroupID(createFrameGroup(rootFrame)); + for (const [stackTraceID, frameMetadata] of frames) { + if (rootFrame.FileID === '' && rootFrame.AddressOrLine === 0) { + // If the root frame is empty, every trace is relevant, and all elements + // of the trace are relevant. This means that the index is set to the + // length of the frameMetadata, implying that in the absence of a root + // frame the "topmost" frame is the root frame. + result.set(stackTraceID, { + frames: frameMetadata, + index: frameMetadata.length, + } as RelevantTrace); + } else { + // Search for the right index of the root frame in the frameMetadata, and + // set it in the result. + for (let i = 0; i < frameMetadata.length; i++) { + if (rootString === createFrameGroupID(createFrameGroup(frameMetadata[i]))) { + result.set(stackTraceID, { + frames: frameMetadata, + index: i, + } as RelevantTrace); + } + } + } + } + return result; +} + +function sortRelevantTraces(relevantTraces: Map): StackTraceID[] { + const sortedRelevantTraces = new Array(); + for (const trace of relevantTraces.keys()) { + sortedRelevantTraces.push(trace); + } + return sortedRelevantTraces.sort((t1, t2) => { + if (t1 < t2) return -1; + if (t1 > t2) return 1; + return 0; + }); +} + +// createCallerCalleeIntermediateRoot creates a graph in the internal +// representation from a StackFrameMetadata that identifies the "centered" +// function and the trace results that provide traces and the number of times +// that the trace has been seen. +// +// The resulting data structure contains all of the data, but is not yet in the +// form most easily digestible by others. +export function createCallerCalleeIntermediateRoot( + rootFrame: StackFrameMetadata, + traces: Map, + frames: Map +): CallerCalleeIntermediateNode { + // Create a node for the centered frame + const root = createCallerCalleeIntermediateNode(rootFrame, 0, 'root'); + + // Obtain only the relevant frames (e.g. frames that contain the root frame + // somewhere). If the root frame is "empty" (e.g. fileID is zero and line + // number is zero), all frames are deemed relevant. + const relevantTraces = selectRelevantTraces(rootFrame, frames); + + // For a deterministic result we have to walk the traces in a deterministic + // order. A deterministic result allows for deterministic UI views, something + // that users expect. + const relevantTracesSorted = sortRelevantTraces(relevantTraces); + + // Walk through all traces that contain the root. Increment the count of the + // root by the count of that trace. Walk "up" the trace (through the callers) + // and add the count of the trace to each caller. Then walk "down" the trace + // (through the callees) and add the count of the trace to each callee. + + for (const traceHash of relevantTracesSorted) { + const trace = relevantTraces.get(traceHash)!; + + // The slice of frames is ordered so that the leaf function is at index 0. + // This means that the "second part" of the slice are the callers, and the + // "first part" are the callees. + // + // We currently assume there are no callers. + const callees = trace.frames; + const samples = traces.get(traceHash)!; + + // Go through the callees, reverse iteration + let currentNode = clone(root); + root.samples += samples; + + for (let i = 0; i < callees.length; i++) { + const callee = callees[i]; + const calleeName = createFrameGroupID(createFrameGroup(callee)); + let node = currentNode.callees.get(calleeName); + if (node === undefined) { + node = createCallerCalleeIntermediateNode(callee, samples, calleeName); + currentNode.callees.set(calleeName, node); + } else { + node.samples += samples; + } + + node.countInclusive += samples; + + if (i === callees.length - 1) { + // Leaf frame: sum up counts for exclusive CPU. + node.countExclusive += samples; + } + currentNode = node; + } + } + + root.countExclusive = 0; + root.countInclusive = root.samples; + + return root; +} + +export interface CallerCalleeNode { + FrameID: string; + FrameGroupID: string; + Callers: CallerCalleeNode[]; + Callees: CallerCalleeNode[]; + FileID: string; + FrameType: number; + ExeFileName: string; + FunctionID: string; + FunctionName: string; + AddressOrLine: number; + FunctionSourceLine: number; + FunctionSourceID: string; + FunctionSourceURL: string; + SourceFilename: string; + SourceLine: number; + Samples: number; + CountInclusive: number; + CountExclusive: number; +} + +export function createCallerCalleeNode(options: Partial = {}): CallerCalleeNode { + const node = {} as CallerCalleeNode; + + node.FrameID = options.FrameID ?? ''; + node.FrameGroupID = options.FrameGroupID ?? ''; + node.Callers = clone(options.Callers ?? []); + node.Callees = clone(options.Callees ?? []); + node.FileID = options.FileID ?? ''; + node.FrameType = options.FrameType ?? 0; + node.ExeFileName = options.ExeFileName ?? ''; + node.FunctionID = options.FunctionID ?? ''; + node.FunctionName = options.FunctionName ?? ''; + node.AddressOrLine = options.AddressOrLine ?? 0; + node.FunctionSourceLine = options.FunctionSourceLine ?? 0; + node.FunctionSourceID = options.FunctionSourceID ?? ''; + node.FunctionSourceURL = options.FunctionSourceURL ?? ''; + node.SourceFilename = options.SourceFilename ?? ''; + node.SourceLine = options.SourceLine ?? 0; + node.Samples = options.Samples ?? 0; + node.CountInclusive = options.CountInclusive ?? 0; + node.CountExclusive = options.CountExclusive ?? 0; + + return node; +} + +// selectCallerCalleeData is the "standard" way of merging multiple frames into +// one node. It simply takes the data from the first frame. +function selectCallerCalleeData(frameMetadata: Set, node: CallerCalleeNode) { + for (const metadata of frameMetadata) { + node.FileID = metadata.FileID; + node.FrameType = metadata.FrameType; + node.ExeFileName = metadata.ExeFileName; + node.FunctionID = metadata.FunctionName; + node.FunctionName = metadata.FunctionName; + node.AddressOrLine = metadata.AddressOrLine; + node.FrameID = metadata.FrameID; + + // Unknown/invalid offsets are currently set to 0. + // + // In this case we leave FunctionSourceLine=0 as a flag for the UI that the + // FunctionSourceLine should not be displayed. + // + // As FunctionOffset=0 could also be a legit value, this work-around needs + // a real fix. The idea for after GA is to change FunctionOffset=-1 to + // indicate unknown/invalid. + if (metadata.FunctionOffset > 0) { + node.FunctionSourceLine = metadata.SourceLine - metadata.FunctionOffset; + } else { + node.FunctionSourceLine = 0; + } + + node.FunctionSourceID = metadata.SourceID; + node.FunctionSourceURL = metadata.SourceCodeURL; + node.SourceFilename = metadata.SourceFilename; + node.SourceLine = metadata.SourceLine; + break; + } +} + +function sortNodes( + nodes: Map +): CallerCalleeIntermediateNode[] { + const sortedNodes = new Array(); + for (const node of nodes.values()) { + sortedNodes.push(node); + } + return sortedNodes.sort((n1, n2) => { + return compareFrameGroup(n1.frameGroup, n2.frameGroup); + }); +} + +// fromCallerCalleeIntermediateNode is used to convert the intermediate representation +// of the diagram into the format that is easily JSONified and more easily consumed by +// others. +export function fromCallerCalleeIntermediateNode( + root: CallerCalleeIntermediateNode +): CallerCalleeNode { + const node = createCallerCalleeNode({ + FrameGroupID: root.frameGroupID, + Samples: root.samples, + CountInclusive: root.countInclusive, + CountExclusive: root.countExclusive, + }); + + // Populate the other fields with data from the root node. Selectors are not supposed + // to be able to fail. + selectCallerCalleeData(root.frameMetadata, node); + + // Now fill the caller and callee arrays. + // For a deterministic result we have to walk the callers / callees in a deterministic + // order. A deterministic result allows deterministic UI views, something that users expect. + for (const caller of sortNodes(root.callers)) { + node.Callers.push(fromCallerCalleeIntermediateNode(caller)); + } + for (const callee of sortNodes(root.callees)) { + node.Callees.push(fromCallerCalleeIntermediateNode(callee)); + } + + return node; +} + +export function createCallerCalleeDiagram( + events: Map, + stackTraces: Map, + stackFrames: Map, + executables: Map +): CallerCalleeNode { + const rootFrame = createStackFrameMetadata(); + const frameMetadataForTraces = groupStackFrameMetadataByStackTrace( + stackTraces, + stackFrames, + executables + ); + const root = createCallerCalleeIntermediateRoot(rootFrame, events, frameMetadataForTraces); + return fromCallerCalleeIntermediateNode(root); +} diff --git a/x-pack/plugins/profiling/common/commonly_used_ranges.ts b/x-pack/plugins/profiling/common/commonly_used_ranges.ts new file mode 100644 index 0000000000000..2323915d17b45 --- /dev/null +++ b/x-pack/plugins/profiling/common/commonly_used_ranges.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. + */ + +export interface CommonlyUsedRange { + start: string; + end: string; + label: string; +} + +export const commonlyUsedRanges: CommonlyUsedRange[] = [ + { + start: 'now-30m', + end: 'now', + label: 'Last 30 minutes', + }, + { + start: 'now-1h', + end: 'now', + label: 'Last hour', + }, + { + start: 'now-24h', + end: 'now', + label: 'Last 24 hours', + }, + { + start: 'now-1w', + end: 'now', + label: 'Last 7 days', + }, + { + start: 'now-30d', + end: 'now', + label: 'Last 30 days', + }, +]; diff --git a/x-pack/plugins/profiling/common/elasticsearch.ts b/x-pack/plugins/profiling/common/elasticsearch.ts new file mode 100644 index 0000000000000..935a148c58667 --- /dev/null +++ b/x-pack/plugins/profiling/common/elasticsearch.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { UnionToIntersection, ValuesType } from 'utility-types'; + +export enum ProfilingESField { + Timestamp = '@timestamp', + ContainerName = 'container.name', + ProcessThreadName = 'process.thread.name', + StacktraceCount = 'Stacktrace.count', + HostID = 'host.id', + HostName = 'host.name', + HostIP = 'host.ip', + OrchestratorResourceName = 'orchestrator.resource.name', + ServiceName = 'service.name', + StacktraceID = 'Stacktrace.id', + StacktraceFrameIDs = 'Stacktrace.frame.ids', + StacktraceFrameTypes = 'Stacktrace.frame.types', + StackframeFileName = 'Stackframe.file.name', + StackframeFunctionName = 'Stackframe.function.name', + StackframeLineNumber = 'Stackframe.line.number', + StackframeFunctionOffset = 'Stackframe.function.offset', + StackframeSourceType = 'Stackframe.source.type', + ExecutableBuildID = 'Executable.build.id', + ExecutableFileName = 'Executable.file.name', +} + +type DedotKey< + TKey extends string | number | symbol, + TValue +> = TKey extends `${infer THead}.${infer TTail}` + ? { + [key in THead]: DedotKey; + } + : { [key in TKey]: TValue }; + +export type DedotObject> = UnionToIntersection< + ValuesType<{ + [TKey in keyof TObject]: DedotKey; + }> +>; + +export type FlattenObject< + TObject extends Record, + TPrefix extends string = '' +> = UnionToIntersection< + ValuesType<{ + [TKey in keyof TObject & string]: TObject[TKey] extends Record + ? FlattenObject + : { [key in `${TPrefix}${TKey}`]: TObject[TKey] }; + }> +>; + +type FlattenedKeysOf> = keyof FlattenObject; + +export type PickFlattened< + TObject extends Record, + TPickKey extends FlattenedKeysOf +> = DedotObject, TPickKey>>; + +export type ProfilingESEvent = DedotObject<{ + [ProfilingESField.Timestamp]: string; + [ProfilingESField.ContainerName]: string; + [ProfilingESField.ProcessThreadName]: string; + [ProfilingESField.StacktraceCount]: number; + [ProfilingESField.HostID]: string; + [ProfilingESField.OrchestratorResourceName]: string; + [ProfilingESField.ServiceName]: string; + [ProfilingESField.StacktraceID]: string; +}>; + +export type ProfilingStackTrace = DedotObject<{ + [ProfilingESField.Timestamp]: number; + [ProfilingESField.StacktraceFrameIDs]: string; + [ProfilingESField.StacktraceFrameTypes]: string; +}>; + +export type ProfilingStackFrame = DedotObject<{ + [ProfilingESField.StackframeFileName]: string; + [ProfilingESField.StackframeFunctionName]: string; + [ProfilingESField.StackframeLineNumber]: number; + [ProfilingESField.StackframeFunctionOffset]: number; + [ProfilingESField.StackframeSourceType]: number; +}>; + +export type ProfilingExecutable = DedotObject<{ + [ProfilingESField.ExecutableBuildID]: string; + [ProfilingESField.ExecutableFileName]: string; + [ProfilingESField.Timestamp]: string; +}>; diff --git a/x-pack/plugins/profiling/common/flamegraph.ts b/x-pack/plugins/profiling/common/flamegraph.ts new file mode 100644 index 0000000000000..b06154dde9c54 --- /dev/null +++ b/x-pack/plugins/profiling/common/flamegraph.ts @@ -0,0 +1,303 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 fnv from 'fnv-plus'; +import { CallerCalleeNode, createCallerCalleeDiagram } from './callercallee'; +import { + describeFrameType, + Executable, + FileID, + StackFrame, + StackFrameID, + StackTrace, + StackTraceID, +} from './profiling'; + +interface ColumnarCallerCallee { + Label: string[]; + Value: number[]; + X: number[]; + Y: number[]; + Color: number[]; + CountInclusive: number[]; + CountExclusive: number[]; + ID: string[]; + FrameID: string[]; + ExecutableID: string[]; +} + +export interface ElasticFlameGraph { + Label: string[]; + Value: number[]; + Position: number[]; + Size: number[]; + Color: number[]; + CountInclusive: number[]; + CountExclusive: number[]; + ID: string[]; + FrameID: string[]; + ExecutableID: string[]; + TotalSeconds: number; + TotalTraces: number; + SampledTraces: number; +} + +export enum FlameGraphComparisonMode { + Absolute = 'absolute', + Relative = 'relative', +} + +/* + * Helper to calculate the color of a given block to be drawn. The desirable outcomes of this are: + * Each of the following frame types should get a different set of color hues: + * + * 0 = Unsymbolized frame + * 1 = Python + * 2 = PHP + * 3 = Native + * 4 = Kernel + * 5 = JVM/Hotspot + * 6 = Ruby + * 7 = Perl + * 8 = JavaScript + * + * This is most easily achieved by mapping frame types to different color variations, using + * the x-position we can use different colors for adjacent blocks while keeping a similar hue + * + * Taken originally from prodfiler_ui/src/helpers/Pixi/frameTypeToColors.tsx + */ +const frameTypeToColors = [ + [0xfd8484, 0xfd9d9d, 0xfeb5b5, 0xfecece], + [0xfcae6b, 0xfdbe89, 0xfdcea6, 0xfedfc4], + [0xfcdb82, 0xfde29b, 0xfde9b4, 0xfef1cd], + [0x6dd0dc, 0x8ad9e3, 0xa7e3ea, 0xc5ecf1], + [0x7c9eff, 0x96b1ff, 0xb0c5ff, 0xcbd8ff], + [0x65d3ac, 0x84dcbd, 0xa3e5cd, 0xc1edde], + [0xd79ffc, 0xdfb2fd, 0xe7c5fd, 0xefd9fe], + [0xf98bb9, 0xfaa2c7, 0xfbb9d5, 0xfdd1e3], + [0xcbc3e3, 0xd5cfe8, 0xdfdbee, 0xeae7f3], +]; + +function frameTypeToRGB(frameType: number, x: number): number { + return frameTypeToColors[frameType][x % 4]; +} + +export function rgbToRGBA(rgb: number): number[] { + return [ + Math.floor(rgb / 65536) / 255, + (Math.floor(rgb / 256) % 256) / 255, + (rgb % 256) / 255, + 1.0, + ]; +} + +function normalize(n: number, lower: number, upper: number): number { + return (n - lower) / (upper - lower); +} + +function checkIfStringHasParentheses(s: string) { + return /\(|\)/.test(s); +} + +function getFunctionName(node: CallerCalleeNode) { + return node.FunctionName !== '' && !checkIfStringHasParentheses(node.FunctionName) + ? `${node.FunctionName}()` + : node.FunctionName; +} + +function getExeFileName(node: CallerCalleeNode) { + if (node?.ExeFileName === undefined) { + return ''; + } + if (node.ExeFileName !== '') { + return node.ExeFileName; + } + return describeFrameType(node.FrameType); +} + +function getLabel(node: CallerCalleeNode) { + if (node.FunctionName !== '') { + const sourceFilename = node.SourceFilename; + const sourceURL = sourceFilename ? sourceFilename.split('/').pop() : ''; + return `${getExeFileName(node)}: ${getFunctionName(node)} in ${sourceURL} #${node.SourceLine}`; + } + return getExeFileName(node); +} + +export class FlameGraph { + // sampleRate is 1/5^N, with N being the downsampled index the events were fetched from. + // N=0: full events table (sampleRate is 1) + // N=1: downsampled by 5 (sampleRate is 0.2) + // ... + sampleRate: number; + + // totalCount is the sum(Count) of all events in the filter range in the + // downsampled index we were looking at. + // To estimate how many events we have in the full events index: totalCount / sampleRate. + // Do the same for single entries in the events array. + totalCount: number; + + totalSeconds: number; + + events: Map; + stacktraces: Map; + stackframes: Map; + executables: Map; + + constructor({ + sampleRate, + totalCount, + events, + stackTraces, + stackFrames, + executables, + totalSeconds, + }: { + sampleRate: number; + totalCount: number; + events: Map; + stackTraces: Map; + stackFrames: Map; + executables: Map; + totalSeconds: number; + }) { + this.sampleRate = sampleRate; + this.totalCount = totalCount; + this.events = events; + this.stacktraces = stackTraces; + this.stackframes = stackFrames; + this.executables = executables; + this.totalSeconds = totalSeconds; + } + + private countCallees(root: CallerCalleeNode): number { + let numCallees = 1; + for (const callee of root.Callees) { + numCallees += this.countCallees(callee); + } + return numCallees; + } + + // createColumnarCallerCallee flattens the intermediate representation of the diagram + // into a columnar format that is more compact than JSON. This representation will later + // need to be normalized into the response ultimately consumed by the flamegraph. + private createColumnarCallerCallee(root: CallerCalleeNode): ColumnarCallerCallee { + const numCallees = this.countCallees(root); + const columnar: ColumnarCallerCallee = { + Label: new Array(numCallees), + Value: new Array(numCallees), + X: new Array(numCallees), + Y: new Array(numCallees), + Color: new Array(numCallees * 4), + CountInclusive: new Array(numCallees), + CountExclusive: new Array(numCallees), + ID: new Array(numCallees), + FrameID: new Array(numCallees), + ExecutableID: new Array(numCallees), + }; + + const queue = [{ x: 0, depth: 1, node: root, parentID: 'root' }]; + + let idx = 0; + while (queue.length > 0) { + const { x, depth, node, parentID } = queue.pop()!; + + if (x === 0 && depth === 1) { + columnar.Label[idx] = 'root: Represents 100% of CPU time.'; + } else { + columnar.Label[idx] = getLabel(node); + } + columnar.Value[idx] = node.Samples; + columnar.X[idx] = x; + columnar.Y[idx] = depth; + + const [red, green, blue, alpha] = rgbToRGBA(frameTypeToRGB(node.FrameType, x)); + const j = 4 * idx; + columnar.Color[j] = red; + columnar.Color[j + 1] = green; + columnar.Color[j + 2] = blue; + columnar.Color[j + 3] = alpha; + + columnar.CountInclusive[idx] = node.CountInclusive; + columnar.CountExclusive[idx] = node.CountExclusive; + + const id = fnv.fast1a64utf(`${parentID}${node.FrameGroupID}`).toString(); + + columnar.ID[idx] = id; + columnar.FrameID[idx] = node.FrameID; + columnar.ExecutableID[idx] = node.FileID; + + node.Callees.sort((a: CallerCalleeNode, b: CallerCalleeNode) => b.Samples - a.Samples); + + let delta = 0; + for (const callee of node.Callees) { + delta += callee.Samples; + } + + for (let i = node.Callees.length - 1; i >= 0; i--) { + delta -= node.Callees[i].Samples; + queue.push({ x: x + delta, depth: depth + 1, node: node.Callees[i], parentID: id }); + } + + idx++; + } + + return columnar; + } + + // createElasticFlameGraph normalizes the intermediate columnar representation into the + // response ultimately consumed by the flamegraph. + private createElasticFlameGraph(columnar: ColumnarCallerCallee): ElasticFlameGraph { + const graph: ElasticFlameGraph = { + Label: [], + Value: [], + Position: [], + Size: [], + Color: [], + CountInclusive: [], + CountExclusive: [], + ID: [], + FrameID: [], + ExecutableID: [], + TotalSeconds: this.totalSeconds, + TotalTraces: Math.floor(this.totalCount / this.sampleRate), + SampledTraces: this.totalCount, + }; + + graph.Label = columnar.Label; + graph.Value = columnar.Value; + graph.Color = columnar.Color; + graph.CountInclusive = columnar.CountInclusive; + graph.CountExclusive = columnar.CountExclusive; + graph.ID = columnar.ID; + graph.FrameID = columnar.FrameID; + graph.ExecutableID = columnar.ExecutableID; + + const maxX = columnar.Value[0]; + const maxY = columnar.Y.reduce((max, n) => (n > max ? n : max), 0); + + for (let i = 0; i < columnar.X.length; i++) { + const x = normalize(columnar.X[i], 0, maxX); + const y = normalize(maxY - columnar.Y[i], 0, maxY); + graph.Position.push(x, y); + } + + graph.Size = graph.Value.map((n) => normalize(n, 0, maxX)); + + return graph; + } + + toElastic(): ElasticFlameGraph { + const root = createCallerCalleeDiagram( + this.events, + this.stacktraces, + this.stackframes, + this.executables + ); + return this.createElasticFlameGraph(this.createColumnarCallerCallee(root)); + } +} diff --git a/x-pack/plugins/profiling/common/frame_group.test.ts b/x-pack/plugins/profiling/common/frame_group.test.ts new file mode 100644 index 0000000000000..57ed29001ccdf --- /dev/null +++ b/x-pack/plugins/profiling/common/frame_group.test.ts @@ -0,0 +1,177 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { compareFrameGroup, createFrameGroup, createFrameGroupID } from './frame_group'; +import { createStackFrameMetadata } from './profiling'; + +const nonSymbolizedFrameGroups = [ + createFrameGroup( + createStackFrameMetadata({ + FileID: '0x0123456789ABCDEF', + AddressOrLine: 102938, + }) + ), + createFrameGroup( + createStackFrameMetadata({ + FileID: '0x0123456789ABCDEF', + AddressOrLine: 1234, + }) + ), + createFrameGroup( + createStackFrameMetadata({ + FileID: '0x0102030405060708', + AddressOrLine: 1234, + }) + ), +]; + +const elfSymbolizedFrameGroups = [ + createFrameGroup( + createStackFrameMetadata({ + FileID: '0x0123456789ABCDEF', + FunctionName: 'strlen()', + }) + ), + createFrameGroup( + createStackFrameMetadata({ + FileID: '0xFEDCBA9876543210', + FunctionName: 'strtok()', + }) + ), + createFrameGroup( + createStackFrameMetadata({ + FileID: '0xFEDCBA9876543210', + FunctionName: 'main()', + }) + ), +]; + +const symbolizedFrameGroups = [ + createFrameGroup( + createStackFrameMetadata({ + ExeFileName: 'chrome', + SourceFilename: 'strlen()', + FunctionName: 'strlen()', + }) + ), + createFrameGroup( + createStackFrameMetadata({ + ExeFileName: 'dockerd', + SourceFilename: 'main()', + FunctionName: 'createTask()', + }) + ), + createFrameGroup( + createStackFrameMetadata({ + ExeFileName: 'oom_reaper', + SourceFilename: 'main()', + FunctionName: 'crash()', + }) + ), +]; + +describe('Frame group operations', () => { + describe('check if a non-symbolized frame group is', () => { + test('less than another non-symbolized frame group', () => { + expect(compareFrameGroup(nonSymbolizedFrameGroups[1], nonSymbolizedFrameGroups[0])).toEqual( + -1 + ); + }); + + test('equal to another non-symbolized frame group', () => { + expect(compareFrameGroup(nonSymbolizedFrameGroups[0], nonSymbolizedFrameGroups[0])).toEqual( + 0 + ); + }); + + test('greater than another non-symbolized frame group', () => { + expect(compareFrameGroup(nonSymbolizedFrameGroups[1], nonSymbolizedFrameGroups[2])).toEqual( + 1 + ); + }); + + test('less than an ELF-symbolized frame group', () => { + expect(compareFrameGroup(nonSymbolizedFrameGroups[1], elfSymbolizedFrameGroups[0])).toEqual( + -1 + ); + }); + + test('less than a symbolized frame group', () => { + expect(compareFrameGroup(nonSymbolizedFrameGroups[1], symbolizedFrameGroups[0])).toEqual(-1); + }); + }); + + describe('check if an ELF-symbolized frame group is', () => { + test('less than another ELF-symbolized frame group', () => { + expect(compareFrameGroup(elfSymbolizedFrameGroups[0], elfSymbolizedFrameGroups[1])).toEqual( + -1 + ); + }); + + test('equal to another ELF-symbolized frame group', () => { + expect(compareFrameGroup(elfSymbolizedFrameGroups[0], elfSymbolizedFrameGroups[0])).toEqual( + 0 + ); + }); + + test('greater than another ELF-symbolized frame group', () => { + expect(compareFrameGroup(elfSymbolizedFrameGroups[1], elfSymbolizedFrameGroups[0])).toEqual( + 1 + ); + }); + + test('greater than a non-symbolized frame group', () => { + expect(compareFrameGroup(elfSymbolizedFrameGroups[0], nonSymbolizedFrameGroups[0])).toEqual( + 1 + ); + }); + + test('less than a symbolized frame group', () => { + expect(compareFrameGroup(elfSymbolizedFrameGroups[2], symbolizedFrameGroups[0])).toEqual(-1); + }); + }); + + describe('check if a symbolized frame group is', () => { + test('less than another symbolized frame group', () => { + expect(compareFrameGroup(symbolizedFrameGroups[0], symbolizedFrameGroups[1])).toEqual(-1); + }); + + test('equal to another symbolized frame group', () => { + expect(compareFrameGroup(symbolizedFrameGroups[0], symbolizedFrameGroups[0])).toEqual(0); + }); + + test('greater than another symbolized frame group', () => { + expect(compareFrameGroup(symbolizedFrameGroups[1], symbolizedFrameGroups[0])).toEqual(1); + }); + + test('greater than a non-symbolized frame group', () => { + expect(compareFrameGroup(symbolizedFrameGroups[0], nonSymbolizedFrameGroups[0])).toEqual(1); + }); + + test('greater than an ELF-symbolized frame group', () => { + expect(compareFrameGroup(symbolizedFrameGroups[0], elfSymbolizedFrameGroups[2])).toEqual(1); + }); + }); + + describe('check serialization for', () => { + test('non-symbolized frame', () => { + expect(createFrameGroupID(nonSymbolizedFrameGroups[0])).toEqual( + 'empty;0x0123456789ABCDEF;102938' + ); + }); + + test('non-symbolized ELF frame', () => { + expect(createFrameGroupID(elfSymbolizedFrameGroups[0])).toEqual( + 'elf;0x0123456789ABCDEF;strlen()' + ); + }); + + test('symbolized frame', () => { + expect(createFrameGroupID(symbolizedFrameGroups[0])).toEqual('full;chrome;strlen();strlen()'); + }); + }); +}); diff --git a/x-pack/plugins/profiling/common/frame_group.ts b/x-pack/plugins/profiling/common/frame_group.ts new file mode 100644 index 0000000000000..776eccaa2e79c --- /dev/null +++ b/x-pack/plugins/profiling/common/frame_group.ts @@ -0,0 +1,163 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { StackFrameMetadata } from './profiling'; + +export type FrameGroupID = string; + +enum FrameGroupName { + EMPTY = 'empty', + ELF = 'elf', + FULL = 'full', +} + +interface BaseFrameGroup { + readonly name: FrameGroupName; +} + +interface EmptyFrameGroup extends BaseFrameGroup { + readonly name: FrameGroupName.EMPTY; + readonly fileID: StackFrameMetadata['FileID']; + readonly addressOrLine: StackFrameMetadata['AddressOrLine']; +} + +interface ElfFrameGroup extends BaseFrameGroup { + readonly name: FrameGroupName.ELF; + readonly fileID: StackFrameMetadata['FileID']; + readonly functionName: StackFrameMetadata['FunctionName']; +} + +interface FullFrameGroup extends BaseFrameGroup { + readonly name: FrameGroupName.FULL; + readonly exeFilename: StackFrameMetadata['ExeFileName']; + readonly functionName: StackFrameMetadata['FunctionName']; + readonly sourceFilename: StackFrameMetadata['SourceFilename']; +} + +export type FrameGroup = EmptyFrameGroup | ElfFrameGroup | FullFrameGroup; + +// createFrameGroup is the "standard" way of grouping frames, by commonly +// shared group identifiers. +// +// For ELF-symbolized frames, group by FunctionName and FileID. +// For non-symbolized frames, group by FileID and AddressOrLine. +// otherwise group by ExeFileName, SourceFilename and FunctionName. +export function createFrameGroup(frame: StackFrameMetadata): FrameGroup { + if (frame.FunctionName === '') { + return { + name: FrameGroupName.EMPTY, + fileID: frame.FileID, + addressOrLine: frame.AddressOrLine, + } as EmptyFrameGroup; + } + + if (frame.SourceFilename === '') { + return { + name: FrameGroupName.ELF, + fileID: frame.FileID, + functionName: frame.FunctionName, + } as ElfFrameGroup; + } + + return { + name: FrameGroupName.FULL, + exeFilename: frame.ExeFileName, + functionName: frame.FunctionName, + sourceFilename: frame.SourceFilename, + } as FullFrameGroup; +} + +// compareFrameGroup compares any two frame groups +// +// In general, frame groups are ordered using the following steps: +// +// * If frame groups are the same type, then we compare using their same +// properties +// * If frame groups have different types, then we compare using overlapping +// properties +// * If frame groups do not share properties, then we compare using the frame +// group type +// +// The union of the properties across all frame group types are ordered below +// from highest to lowest. For instance, given any two frame groups, shared +// properties are compared in the given order: +// +// * exeFilename +// * sourceFilename +// * functionName +// * fileID +// * addressOrLine +// +// Frame group types are ordered according to how much symbolization metadata +// is available, starting from most to least: +// +// * Symbolized frame group +// * ELF-symbolized frame group +// * Unsymbolized frame group +export function compareFrameGroup(a: FrameGroup, b: FrameGroup): number { + if (a.name === FrameGroupName.EMPTY) { + if (b.name === FrameGroupName.EMPTY) { + if (a.fileID < b.fileID) return -1; + if (a.fileID > b.fileID) return 1; + if (a.addressOrLine < b.addressOrLine) return -1; + if (a.addressOrLine > b.addressOrLine) return 1; + return 0; + } + if (b.name === FrameGroupName.ELF) { + if (a.fileID < b.fileID) return -1; + if (a.fileID > b.fileID) return 1; + } + return -1; + } + + if (a.name === FrameGroupName.ELF) { + if (b.name === FrameGroupName.EMPTY) { + if (a.fileID < b.fileID) return -1; + if (a.fileID > b.fileID) return 1; + return 1; + } + if (b.name === FrameGroupName.ELF) { + if (a.functionName < b.functionName) return -1; + if (a.functionName > b.functionName) return 1; + if (a.fileID < b.fileID) return -1; + if (a.fileID > b.fileID) return 1; + return 0; + } + if (a.functionName < b.functionName) return -1; + if (a.functionName > b.functionName) return 1; + return -1; + } + + if (b.name === FrameGroupName.FULL) { + if (a.exeFilename < b.exeFilename) return -1; + if (a.exeFilename > b.exeFilename) return 1; + if (a.sourceFilename < b.sourceFilename) return -1; + if (a.sourceFilename > b.sourceFilename) return 1; + if (a.functionName < b.functionName) return -1; + if (a.functionName > b.functionName) return 1; + return 0; + } + if (b.name === FrameGroupName.ELF) { + if (a.functionName < b.functionName) return -1; + if (a.functionName > b.functionName) return 1; + } + return 1; +} + +export function createFrameGroupID(frameGroup: FrameGroup): FrameGroupID { + switch (frameGroup.name) { + case FrameGroupName.EMPTY: + return `${frameGroup.name};${frameGroup.fileID};${frameGroup.addressOrLine}`; + break; + case FrameGroupName.ELF: + return `${frameGroup.name};${frameGroup.fileID};${frameGroup.functionName}`; + break; + case FrameGroupName.FULL: + return `${frameGroup.name};${frameGroup.exeFilename};${frameGroup.functionName};${frameGroup.sourceFilename}`; + break; + } +} diff --git a/x-pack/plugins/profiling/common/functions.test.ts b/x-pack/plugins/profiling/common/functions.test.ts new file mode 100644 index 0000000000000..c17687453c2fd --- /dev/null +++ b/x-pack/plugins/profiling/common/functions.test.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 { createTopNFunctions } from './functions'; + +import { events, stackTraces, stackFrames, executables } from './__fixtures__/stacktraces'; +import { sum } from 'lodash'; + +describe('TopN function operations', () => { + test('1', () => { + const maxTopN = 5; + const totalSamples = sum([...events.values()]); + const topNFunctions = createTopNFunctions( + events, + stackTraces, + stackFrames, + executables, + 0, + maxTopN + ); + + expect(topNFunctions.TotalCount).toEqual(totalSamples); + expect(topNFunctions.TopN.length).toEqual(maxTopN); + + const exclusiveCounts = topNFunctions.TopN.map((value) => value.CountExclusive); + expect(exclusiveCounts).toEqual([16, 9, 7, 5, 2]); + }); +}); diff --git a/x-pack/plugins/profiling/common/functions.ts b/x-pack/plugins/profiling/common/functions.ts new file mode 100644 index 0000000000000..5b45e3fcfaa7f --- /dev/null +++ b/x-pack/plugins/profiling/common/functions.ts @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 t from 'io-ts'; +import { + compareFrameGroup, + createFrameGroup, + createFrameGroupID, + FrameGroup, + FrameGroupID, +} from './frame_group'; +import { + Executable, + FileID, + groupStackFrameMetadataByStackTrace, + StackFrame, + StackFrameID, + StackFrameMetadata, + StackTrace, + StackTraceID, +} from './profiling'; + +interface TopNFunctionAndFrameGroup { + Frame: StackFrameMetadata; + FrameGroup: FrameGroup; + CountExclusive: number; + CountInclusive: number; +} + +type TopNFunction = Pick< + TopNFunctionAndFrameGroup, + 'Frame' | 'CountExclusive' | 'CountInclusive' +> & { Id: string; Rank: number }; + +export interface TopNFunctions { + TotalCount: number; + TopN: TopNFunction[]; +} + +export function createTopNFunctions( + events: Map, + stackTraces: Map, + stackFrames: Map, + executables: Map, + startIndex: number, + endIndex: number +): TopNFunctions { + const metadata = groupStackFrameMetadataByStackTrace(stackTraces, stackFrames, executables); + + // The `count` associated with a frame provides the total number of + // traces in which that node has appeared at least once. However, a + // frame may appear multiple times in a trace, and thus to avoid + // counting it multiple times we need to record the frames seen so + // far in each trace. + let totalCount = 0; + const topNFunctions = new Map(); + + // Collect metadata and inclusive + exclusive counts for each distinct frame. + for (const [traceHash, count] of events) { + const uniqueFrameGroupsPerEvent = new Set(); + + totalCount += count; + + // It is possible that we do not have a stacktrace for an event, + // e.g. when stopping the host agent or on network errors. + const frames = metadata.get(traceHash) ?? []; + for (let i = 0; i < frames.length; i++) { + const frameGroup = createFrameGroup(frames[i]); + const frameGroupID = createFrameGroupID(frameGroup); + + if (!topNFunctions.has(frameGroupID)) { + topNFunctions.set(frameGroupID, { + Frame: frames[i], + FrameGroup: frameGroup, + CountExclusive: 0, + CountInclusive: 0, + }); + } + + const topNFunction = topNFunctions.get(frameGroupID)!; + + if (!uniqueFrameGroupsPerEvent.has(frameGroupID)) { + uniqueFrameGroupsPerEvent.add(frameGroupID); + topNFunction.CountInclusive += count; + } + + if (i === frames.length - 1) { + // Leaf frame: sum up counts for exclusive CPU. + topNFunction.CountExclusive += count; + } + } + } + + // Sort in descending order by exclusive CPU. Same values should appear in a + // stable order, so compare the FrameGroup in this case. + const topN = [...topNFunctions.values()]; + topN + .sort((a: TopNFunctionAndFrameGroup, b: TopNFunctionAndFrameGroup) => { + if (a.CountExclusive > b.CountExclusive) { + return 1; + } + if (a.CountExclusive < b.CountExclusive) { + return -1; + } + return compareFrameGroup(a.FrameGroup, b.FrameGroup); + }) + .reverse(); + + if (startIndex > topN.length) { + startIndex = topN.length; + } + if (endIndex > topN.length) { + endIndex = topN.length; + } + + const framesAndCountsAndIds = topN.slice(startIndex, endIndex).map((frameAndCount, i) => ({ + Rank: i + 1, + Frame: frameAndCount.Frame, + CountExclusive: frameAndCount.CountExclusive, + CountInclusive: frameAndCount.CountInclusive, + Id: createFrameGroupID(frameAndCount.FrameGroup), + })); + + return { + TotalCount: totalCount, + TopN: framesAndCountsAndIds, + }; +} + +export enum TopNFunctionSortField { + Rank = 'rank', + Frame = 'frame', + Samples = 'samples', + ExclusiveCPU = 'exclusiveCPU', + InclusiveCPU = 'inclusiveCPU', + Diff = 'diff', +} + +export const topNFunctionSortFieldRt = t.union([ + t.literal(TopNFunctionSortField.Rank), + t.literal(TopNFunctionSortField.Frame), + t.literal(TopNFunctionSortField.Samples), + t.literal(TopNFunctionSortField.ExclusiveCPU), + t.literal(TopNFunctionSortField.InclusiveCPU), + t.literal(TopNFunctionSortField.Diff), +]); diff --git a/x-pack/plugins/profiling/common/histogram.ts b/x-pack/plugins/profiling/common/histogram.ts new file mode 100644 index 0000000000000..1d18e0342891d --- /dev/null +++ b/x-pack/plugins/profiling/common/histogram.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 { range } from 'lodash'; + +export function computeBucketWidthFromTimeRangeAndBucketCount( + timeFrom: number, + timeTo: number, + numBuckets: number +): number { + return Math.max(Math.floor((timeTo - timeFrom) / numBuckets), 1); +} + +// Given a possibly empty set of timestamps, a time range, and a bucket width, +// we create an increasing list of timestamps that are uniformally spaced and +// cover the given time range. +// +// The smallest timestamp, t0, should match this invariant: +// timeFrom - bucketWidth < t0 <= timeFrom +// +// The largest timestamp, t1, should match this invariant: +// timeTo - bucketWidth < t1 <= timeTo +export function createUniformBucketsForTimeRange( + timestamps: number[], + timeFrom: number, + timeTo: number, + bucketWidth: number +): number[] { + if (timestamps.length > 0) { + // We only need one arbitrary timestamp to generate the buckets covering + // the given time range + const t = timestamps[0]; + const left = t - bucketWidth * Math.ceil((t - timeFrom) / bucketWidth); + const right = t + bucketWidth * Math.floor((timeTo - t) / bucketWidth); + return range(left, right + 1, bucketWidth); + } + return range(timeFrom, timeTo + 1, bucketWidth); +} diff --git a/x-pack/plugins/profiling/common/index.test.ts b/x-pack/plugins/profiling/common/index.test.ts new file mode 100644 index 0000000000000..8fd482ce97598 --- /dev/null +++ b/x-pack/plugins/profiling/common/index.test.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 { timeRangeFromRequest } from '.'; + +describe('Common profiling helpers', () => { + test('convert query parameters time ranges into tuple', () => { + const request = { + query: { + timeFrom: 123, + timeTo: 456, + }, + }; + expect(timeRangeFromRequest(request)).toEqual([123, 456]); + }); +}); diff --git a/x-pack/plugins/profiling/common/index.ts b/x-pack/plugins/profiling/common/index.ts new file mode 100644 index 0000000000000..871bc9ee1cd91 --- /dev/null +++ b/x-pack/plugins/profiling/common/index.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const PLUGIN_ID = 'profiling'; +export const PLUGIN_NAME = 'profiling'; + +export const INDEX_EVENTS = 'profiling-events-all'; +export const INDEX_TRACES = 'profiling-stacktraces'; +export const INDEX_FRAMES = 'profiling-stackframes'; +export const INDEX_EXECUTABLES = 'profiling-executables'; + +const BASE_ROUTE_PATH = '/api/profiling/v1'; + +export function getRoutePaths() { + return { + TopN: `${BASE_ROUTE_PATH}/topn`, + TopNContainers: `${BASE_ROUTE_PATH}/topn/containers`, + TopNDeployments: `${BASE_ROUTE_PATH}/topn/deployments`, + TopNFunctions: `${BASE_ROUTE_PATH}/topn/functions`, + TopNHosts: `${BASE_ROUTE_PATH}/topn/hosts`, + TopNThreads: `${BASE_ROUTE_PATH}/topn/threads`, + TopNTraces: `${BASE_ROUTE_PATH}/topn/traces`, + Flamechart: `${BASE_ROUTE_PATH}/flamechart`, + FrameInformation: `${BASE_ROUTE_PATH}/frame_information`, + }; +} + +export function timeRangeFromRequest(request: any): [number, number] { + const timeFrom = parseInt(request.query.timeFrom!, 10); + const timeTo = parseInt(request.query.timeTo!, 10); + return [timeFrom, timeTo]; +} + +// Converts from a Map object to a Record object since Map objects are not +// serializable to JSON by default +export function fromMapToRecord(m: Map): Record { + const output: Record = {}; + + for (const [key, value] of m) { + output[key] = value; + } + + return output; +} + +export const NOT_AVAILABLE_LABEL = i18n.translate('xpack.profiling.notAvailableLabel', { + defaultMessage: 'N/A', +}); diff --git a/x-pack/plugins/profiling/common/profiling.test.ts b/x-pack/plugins/profiling/common/profiling.test.ts new file mode 100644 index 0000000000000..4f7b1fb3b8fb3 --- /dev/null +++ b/x-pack/plugins/profiling/common/profiling.test.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 { + createStackFrameMetadata, + FrameType, + getCalleeFunction, + getCalleeSource, +} from './profiling'; + +describe('Stack frame metadata operations', () => { + test('metadata has executable and function names', () => { + const metadata = createStackFrameMetadata({ + ExeFileName: 'chrome', + FrameType: FrameType.Native, + FunctionName: 'strlen()', + }); + expect(getCalleeFunction(metadata)).toEqual('chrome: strlen()'); + }); + + test('metadata only has executable name', () => { + const metadata = createStackFrameMetadata({ + ExeFileName: 'promtail', + FrameType: FrameType.Native, + }); + expect(getCalleeFunction(metadata)).toEqual('promtail'); + }); + + test('metadata has executable name but no function name or source line', () => { + const metadata = createStackFrameMetadata({ + ExeFileName: 'promtail', + FrameType: FrameType.Native, + }); + expect(getCalleeSource(metadata)).toEqual('promtail+0x0'); + }); + + test('metadata has no executable name, function name, or source line', () => { + const metadata = createStackFrameMetadata({}); + expect(getCalleeSource(metadata)).toEqual(''); + }); + + test('metadata has source name but no source line', () => { + const metadata = createStackFrameMetadata({ + ExeFileName: 'dockerd', + FrameType: FrameType.Native, + SourceFilename: 'dockerd', + FunctionOffset: 0x183a5b0, + }); + expect(getCalleeSource(metadata)).toEqual('dockerd+0x0'); + }); + + test('metadata has source name and function offset', () => { + const metadata = createStackFrameMetadata({ + ExeFileName: 'python3.9', + FrameType: FrameType.Python, + FunctionName: 'PyDict_GetItemWithError', + FunctionOffset: 2567, + SourceFilename: '/build/python3.9-RNBry6/python3.9-3.9.2/Objects/dictobject.c', + SourceLine: 1456, + }); + expect(getCalleeSource(metadata)).toEqual( + '/build/python3.9-RNBry6/python3.9-3.9.2/Objects/dictobject.c#1456' + ); + }); + + test('metadata has source name but no function offset', () => { + const metadata = createStackFrameMetadata({ + ExeFileName: 'agent', + FrameType: FrameType.Native, + FunctionName: 'runtime.mallocgc', + SourceFilename: 'runtime/malloc.go', + }); + expect(getCalleeSource(metadata)).toEqual('runtime/malloc.go'); + }); +}); diff --git a/x-pack/plugins/profiling/common/profiling.ts b/x-pack/plugins/profiling/common/profiling.ts new file mode 100644 index 0000000000000..003f6565677c1 --- /dev/null +++ b/x-pack/plugins/profiling/common/profiling.ts @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 StackTraceID = string; +export type StackFrameID = string; +export type FileID = string; + +export function createStackFrameID(fileID: FileID, addressOrLine: number): StackFrameID { + const buf = Buffer.alloc(24); + Buffer.from(fileID, 'base64url').copy(buf); + buf.writeBigUInt64BE(BigInt(addressOrLine), 16); + return buf.toString('base64url'); +} + +export enum FrameType { + Unsymbolized = 0, + Python, + PHP, + Native, + Kernel, + JVM, + Ruby, + Perl, + JavaScript, +} + +export function describeFrameType(ft: FrameType): string { + return { + [FrameType.Unsymbolized]: '', + [FrameType.Python]: 'Python', + [FrameType.PHP]: 'PHP', + [FrameType.Native]: 'Native', + [FrameType.Kernel]: 'Kernel', + [FrameType.JVM]: 'JVM/Hotspot', + [FrameType.Ruby]: 'Ruby', + [FrameType.Perl]: 'Perl', + [FrameType.JavaScript]: 'JavaScript', + }[ft]; +} + +export interface StackTraceEvent { + StackTraceID: StackTraceID; + Count: number; +} + +export interface StackTrace { + FrameIDs: string[]; + FileIDs: string[]; + AddressOrLines: number[]; + Types: number[]; +} + +export interface StackFrame { + FileName: string; + FunctionName: string; + FunctionOffset: number; + LineNumber: number; + SourceType: number; +} + +export interface Executable { + FileName: string; +} + +export interface StackFrameMetadata { + // StackTrace.FrameID + FrameID: string; + // StackTrace.FileID + FileID: FileID; + // StackTrace.Type + FrameType: FrameType; + + // StackFrame.LineNumber? + AddressOrLine: number; + // StackFrame.FunctionName + FunctionName: string; + // StackFrame.FunctionOffset + FunctionOffset: number; + // should this be StackFrame.SourceID? + SourceID: FileID; + // StackFrame.Filename + SourceFilename: string; + // StackFrame.LineNumber + SourceLine: number; + + // Executable.FileName + ExeFileName: string; + + // unused atm due to lack of symbolization metadata + CommitHash: string; + // unused atm due to lack of symbolization metadata + SourceCodeURL: string; + // unused atm due to lack of symbolization metadata + SourcePackageHash: string; + // unused atm due to lack of symbolization metadata + SourcePackageURL: string; + // unused atm due to lack of symbolization metadata + SourceType: number; +} + +export function createStackFrameMetadata( + options: Partial = {} +): StackFrameMetadata { + const metadata = {} as StackFrameMetadata; + + metadata.FrameID = options.FrameID ?? ''; + metadata.FileID = options.FileID ?? ''; + metadata.FrameType = options.FrameType ?? 0; + metadata.AddressOrLine = options.AddressOrLine ?? 0; + metadata.FunctionName = options.FunctionName ?? ''; + metadata.FunctionOffset = options.FunctionOffset ?? 0; + metadata.SourceID = options.SourceID ?? ''; + metadata.SourceLine = options.SourceLine ?? 0; + metadata.ExeFileName = options.ExeFileName ?? ''; + metadata.CommitHash = options.CommitHash ?? ''; + metadata.SourceCodeURL = options.SourceCodeURL ?? ''; + metadata.SourceFilename = options.SourceFilename ?? ''; + metadata.SourcePackageHash = options.SourcePackageHash ?? ''; + metadata.SourcePackageURL = options.SourcePackageURL ?? ''; + metadata.SourceType = options.SourceType ?? 0; + + return metadata; +} + +export function getCalleeFunction(frame: StackFrameMetadata): string { + // In the best case scenario, we have the file names, source lines, + // and function names. However we need to deal with missing function or + // executable info. + const exeDisplayName = frame.ExeFileName ? frame.ExeFileName : describeFrameType(frame.FrameType); + + // When there is no function name, only use the executable name + return frame.FunctionName ? exeDisplayName + ': ' + frame.FunctionName : exeDisplayName; +} + +export function getCalleeSource(frame: StackFrameMetadata): string { + if (frame.FunctionName === '' && frame.SourceLine === 0) { + if (frame.ExeFileName) { + // If no source line or filename available, display the executable offset + return frame.ExeFileName + '+0x' + frame.AddressOrLine.toString(16); + } + + // If we don't have the executable filename, display + return ''; + } + + if (frame.SourceFilename !== '' && frame.SourceLine === 0) { + return frame.SourceFilename; + } + + return frame.SourceFilename + (frame.SourceLine !== 0 ? `#${frame.SourceLine}` : ''); +} + +// groupStackFrameMetadataByStackTrace collects all of the per-stack-frame +// metadata for a given set of trace IDs and their respective stack frames. +// +// This is similar to GetTraceMetaData in pf-storage-backend/storagebackend/storagebackendv1/reads_webservice.go +export function groupStackFrameMetadataByStackTrace( + stackTraces: Map, + stackFrames: Map, + executables: Map +): Map { + const frameMetadataForTraces = new Map(); + for (const [stackTraceID, trace] of stackTraces) { + const frameMetadata = new Array(); + for (let i = 0; i < trace.FrameIDs.length; i++) { + const frameID = trace.FrameIDs[i]; + const fileID = trace.FileIDs[i]; + const addressOrLine = trace.AddressOrLines[i]; + const frame = stackFrames.get(frameID)!; + const executable = executables.get(fileID)!; + + const metadata = createStackFrameMetadata({ + FrameID: frameID, + FileID: fileID, + AddressOrLine: addressOrLine, + FrameType: trace.Types[i], + FunctionName: frame.FunctionName, + FunctionOffset: frame.FunctionOffset, + SourceLine: frame.LineNumber, + SourceFilename: frame.FileName, + ExeFileName: executable.FileName, + }); + + frameMetadata.push(metadata); + } + frameMetadataForTraces.set(stackTraceID, frameMetadata); + } + return frameMetadataForTraces; +} diff --git a/x-pack/plugins/profiling/common/runtime_types/range_rt.ts b/x-pack/plugins/profiling/common/runtime_types/range_rt.ts new file mode 100644 index 0000000000000..9231f1c49219a --- /dev/null +++ b/x-pack/plugins/profiling/common/runtime_types/range_rt.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import * as t from 'io-ts'; + +export const rangeRt = t.type({ + rangeFrom: t.string, + rangeTo: t.string, +}); diff --git a/x-pack/plugins/profiling/common/stack_traces.ts b/x-pack/plugins/profiling/common/stack_traces.ts new file mode 100644 index 0000000000000..8879728c21530 --- /dev/null +++ b/x-pack/plugins/profiling/common/stack_traces.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 { ProfilingESField } from './elasticsearch'; + +export enum StackTracesDisplayOption { + StackTraces = 'stackTraces', + Percentage = 'percentage', +} + +export enum TopNType { + Containers = 'containers', + Deployments = 'deployments', + Threads = 'threads', + Hosts = 'hosts', + Traces = 'traces', +} + +export function getFieldNameForTopNType(type: TopNType): string { + return { + [TopNType.Containers]: ProfilingESField.ContainerName, + [TopNType.Deployments]: ProfilingESField.OrchestratorResourceName, + [TopNType.Threads]: ProfilingESField.ProcessThreadName, + [TopNType.Hosts]: ProfilingESField.HostID, + [TopNType.Traces]: ProfilingESField.StacktraceID, + }[type]; +} diff --git a/x-pack/plugins/profiling/common/topn.ts b/x-pack/plugins/profiling/common/topn.ts new file mode 100644 index 0000000000000..85b57cdc41e00 --- /dev/null +++ b/x-pack/plugins/profiling/common/topn.ts @@ -0,0 +1,271 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { euiPaletteColorBlind } from '@elastic/eui'; +import { InferSearchResponseOf } from '@kbn/core/types/elasticsearch'; +import { i18n } from '@kbn/i18n'; +import { orderBy } from 'lodash'; +import { ProfilingESField } from './elasticsearch'; +import { createUniformBucketsForTimeRange } from './histogram'; +import { StackFrameMetadata } from './profiling'; + +export const OTHER_BUCKET_LABEL = i18n.translate('xpack.profiling.topn.otherBucketLabel', { + defaultMessage: 'Other', +}); + +export interface CountPerTime { + Timestamp: number; + Count: number | null; +} + +export interface TopNSample extends CountPerTime { + Category: string; +} + +export interface TopNSamples { + TopN: TopNSample[]; +} + +export interface TopNResponse extends TopNSamples { + TotalCount: number; + Metadata: Record; + Labels: Record; +} + +export interface TopNSamplesHistogramResponse { + sum_other_doc_count: number; + buckets: Array<{ + key: string | number; + doc_count: number; + count: { value: number | null }; + over_time: { + buckets: Array<{ doc_count: number; key: string | number; count: { value: number | null } }>; + }; + }>; +} + +export function getTopNAggregationRequest({ + searchField, + highCardinality, + fixedInterval, +}: { + searchField: string; + highCardinality: boolean; + fixedInterval: string; +}) { + return { + group_by: { + terms: { + field: searchField, + order: { count: 'desc' as const }, + size: 99, + execution_hint: highCardinality ? ('map' as const) : ('global_ordinals' as const), + }, + aggs: { + ...(searchField === ProfilingESField.HostID + ? { + sample: { + top_metrics: { + metrics: [ + { field: ProfilingESField.HostName }, + { field: ProfilingESField.HostIP }, + ] as [{ field: ProfilingESField.HostName }, { field: ProfilingESField.HostIP }], + sort: { + '@timestamp': 'desc' as const, + }, + }, + }, + } + : {}), + over_time: { + date_histogram: { + field: ProfilingESField.Timestamp, + fixed_interval: fixedInterval, + }, + aggs: { + count: { + sum: { + field: ProfilingESField.StacktraceCount, + }, + }, + }, + }, + count: { + sum: { + field: ProfilingESField.StacktraceCount, + }, + }, + }, + }, + over_time: { + date_histogram: { + field: ProfilingESField.Timestamp, + fixed_interval: fixedInterval, + }, + aggs: { + count: { + sum: { + field: ProfilingESField.StacktraceCount, + }, + }, + }, + }, + total_count: { + sum_bucket: { + buckets_path: 'over_time>count', + }, + }, + }; +} + +export function createTopNSamples( + response: Required< + InferSearchResponseOf } }> + >['aggregations'], + startMilliseconds: number, + endMilliseconds: number, + bucketWidth: number +): TopNSample[] { + const bucketsByCategories = new Map(); + const uniqueTimestamps = new Set(); + const groupByBuckets = response.group_by.buckets ?? []; + + // Keep track of the sum per timestamp to subtract it from the 'other' bucket + const sumsOfKnownFieldsByTimestamp = new Map(); + + // Convert the buckets into nested maps and record the unique timestamps + for (let i = 0; i < groupByBuckets.length; i++) { + const frameCountsByTimestamp = new Map(); + const items = groupByBuckets[i].over_time.buckets; + + for (let j = 0; j < items.length; j++) { + const timestamp = Number(items[j].key); + const count = items[j].count.value ?? 0; + uniqueTimestamps.add(timestamp); + const sumAtTimestamp = (sumsOfKnownFieldsByTimestamp.get(timestamp) ?? 0) + count; + sumsOfKnownFieldsByTimestamp.set(timestamp, sumAtTimestamp); + frameCountsByTimestamp.set(timestamp, count); + } + bucketsByCategories.set(groupByBuckets[i].key, frameCountsByTimestamp); + } + + // Create the 'other' bucket by subtracting the sum of all known buckets + // from the total + const otherFrameCountsByTimestamp = new Map(); + + let addOtherBucket = false; + + for (let i = 0; i < response.over_time.buckets.length; i++) { + const bucket = response.over_time.buckets[i]; + const timestamp = Number(bucket.key); + const valueForOtherBucket = + (bucket.count.value ?? 0) - (sumsOfKnownFieldsByTimestamp.get(timestamp) ?? 0); + + if (valueForOtherBucket > 0) { + addOtherBucket = true; + } + + otherFrameCountsByTimestamp.set(timestamp, valueForOtherBucket); + } + + // Only add the 'other' bucket if at least one value per timestamp is > 0 + if (addOtherBucket) { + bucketsByCategories.set(OTHER_BUCKET_LABEL, otherFrameCountsByTimestamp); + } + + // Fill in missing timestamps so that the entire time range is covered + const timestamps = createUniformBucketsForTimeRange( + [...uniqueTimestamps], + startMilliseconds, + endMilliseconds, + bucketWidth + ); + + // Normalize samples so there are an equal number of data points per timestamp + const samples: TopNSample[] = []; + for (const category of bucketsByCategories.keys()) { + const frameCountsByTimestamp = bucketsByCategories.get(category); + for (const timestamp of timestamps) { + const sample: TopNSample = { + Timestamp: timestamp, + Count: frameCountsByTimestamp.get(timestamp) ?? 0, + Category: category, + }; + samples.push(sample); + } + } + + return orderBy(samples, ['Timestamp', 'Count', 'Category'], ['asc', 'desc', 'asc']); +} + +export interface TopNSubchart { + Category: string; + Label: string; + Percentage: number; + Series: CountPerTime[]; + Color: string; + Index: number; + Metadata: StackFrameMetadata[]; +} + +export function groupSamplesByCategory({ + samples, + totalCount, + metadata, + labels, +}: { + samples: TopNSample[]; + totalCount: number; + metadata: Record; + labels: Record; +}): TopNSubchart[] { + const seriesByCategory = new Map(); + + for (let i = 0; i < samples.length; i++) { + const sample = samples[i]; + + if (!seriesByCategory.has(sample.Category)) { + seriesByCategory.set(sample.Category, []); + } + const series = seriesByCategory.get(sample.Category)!; + series.push({ + Timestamp: sample.Timestamp, + Count: sample.Count, + }); + } + + const subcharts: Array> = []; + + for (const [category, series] of seriesByCategory) { + const totalPerCategory = series.reduce((sumOf, { Count }) => sumOf + (Count ?? 0), 0); + subcharts.push({ + Category: category, + Label: labels[category] || category, + Percentage: (totalPerCategory / totalCount) * 100, + Series: series, + Metadata: metadata[category] ?? [], + }); + } + + const colors = euiPaletteColorBlind({ + rotations: Math.ceil(subcharts.length / 10), + }); + + return orderBy(subcharts, ['Percentage', 'Category'], ['desc', 'asc']).map((chart, index) => { + return { + ...chart, + Color: colors[index], + Index: index + 1, + Series: chart.Series.map((value) => { + return { + ...value, + Category: chart.Category, + }; + }), + }; + }); +} diff --git a/x-pack/plugins/profiling/common/types.ts b/x-pack/plugins/profiling/common/types.ts new file mode 100644 index 0000000000000..83efba8cb813a --- /dev/null +++ b/x-pack/plugins/profiling/common/types.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface TimeRange { + start: string; + end: string; +} diff --git a/x-pack/plugins/profiling/jest.config.js b/x-pack/plugins/profiling/jest.config.js new file mode 100644 index 0000000000000..783ace59b9b3d --- /dev/null +++ b/x-pack/plugins/profiling/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/profiling'], + coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/profiling', + coverageReporters: ['text', 'html'], + collectCoverageFrom: ['/x-pack/plugins/profiling/{common,public,server}/**/*.{ts,tsx}'], +}; diff --git a/x-pack/plugins/profiling/kibana.json b/x-pack/plugins/profiling/kibana.json new file mode 100644 index 0000000000000..0ead3e39f83f6 --- /dev/null +++ b/x-pack/plugins/profiling/kibana.json @@ -0,0 +1,29 @@ +{ + "id": "profiling", + "version": "1.0.0", + "kibanaVersion": "kibana", + "owner": { + "name": "profiling", + "githubTeam": "profiling-ui" + }, + "description": "", + "server": true, + "ui": true, + "requiredPlugins": [ + "navigation", + "data", + "kibanaUtils", + "share", + "observability", + "features", + "kibanaReact", + "unifiedSearch", + "dataViews", + "charts" + ], + "optionalPlugins": [], + "configPath": [ + "xpack", + "profiling" + ] +} diff --git a/x-pack/plugins/profiling/public/app.tsx b/x-pack/plugins/profiling/public/app.tsx new file mode 100644 index 0000000000000..7ab281efd9b9a --- /dev/null +++ b/x-pack/plugins/profiling/public/app.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { AppMountParameters, CoreSetup, CoreStart } from '@kbn/core/public'; +import React, { useMemo } from 'react'; +import ReactDOM from 'react-dom'; + +import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { RouteRenderer, RouterProvider } from '@kbn/typed-react-router-config'; + +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import { css } from '@emotion/react'; +import { ProfilingDependenciesContextProvider } from './components/contexts/profiling_dependencies/profiling_dependencies_context'; +import { RedirectWithDefaultDateRange } from './components/redirect_with_default_date_range'; +import { profilingRouter } from './routing'; +import { Services } from './services'; +import { ProfilingPluginPublicSetupDeps, ProfilingPluginPublicStartDeps } from './types'; +import { RouteBreadcrumbsContextProvider } from './components/contexts/route_breadcrumbs_context'; +import { TimeRangeContextProvider } from './components/contexts/time_range_context'; + +interface Props { + profilingFetchServices: Services; + coreStart: CoreStart; + coreSetup: CoreSetup; + pluginsStart: ProfilingPluginPublicStartDeps; + pluginsSetup: ProfilingPluginPublicSetupDeps; + theme$: AppMountParameters['theme$']; + history: AppMountParameters['history']; +} + +const redirectAppLinksCss = css` + display: flex; + flex-grow: 1; +`; + +const storage = new Storage(localStorage); + +function App({ + coreStart, + coreSetup, + pluginsStart, + pluginsSetup, + profilingFetchServices, + theme$, + history, +}: Props) { + const i18nCore = coreStart.i18n; + + const profilingDependencies = useMemo(() => { + return { + start: { + core: coreStart, + ...pluginsStart, + }, + setup: { + core: coreSetup, + ...pluginsSetup, + }, + services: profilingFetchServices, + }; + }, [coreStart, coreSetup, pluginsStart, pluginsSetup, profilingFetchServices]); + + return ( + + + + + + + + + + + + + + + + + + + + ); +} + +export const renderApp = (props: Props, element: AppMountParameters['element']) => { + ReactDOM.render(, element); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/x-pack/plugins/profiling/public/components/async_component.tsx b/x-pack/plugins/profiling/public/components/async_component.tsx new file mode 100644 index 0000000000000..2dba7ed2ab3cf --- /dev/null +++ b/x-pack/plugins/profiling/public/components/async_component.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 { EuiFlexGroup, EuiFlexItem, EuiLoadingChart, EuiText, EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { AsyncState, AsyncStatus } from '../hooks/use_async'; + +export function AsyncComponent({ + children, + status, + error, + mono, + size, + style, + alignTop, +}: AsyncState & { + style?: React.ComponentProps['style']; + children: React.ReactElement; + mono?: boolean; + size: 'm' | 'l' | 'xl'; + alignTop?: boolean; +}) { + if (status === AsyncStatus.Settled && !error) { + return children; + } + + return ( + + + {error && status === AsyncStatus.Settled ? ( + + + + + + + {i18n.translate('xpack.profiling.asyncComponent.errorLoadingData', { + defaultMessage: 'Could not load data', + })} + + + + ) : ( + + )} + + + ); +} diff --git a/x-pack/plugins/profiling/public/components/chart_grid.tsx b/x-pack/plugins/profiling/public/components/chart_grid.tsx new file mode 100644 index 0000000000000..ac6cab8dd4102 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/chart_grid.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGrid, EuiFlexItem, EuiFlyout, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { take } from 'lodash'; +import React, { useState } from 'react'; +import { TopNSubchart } from '../../common/topn'; +import { SubChart } from './subchart'; + +export interface ChartGridProps { + limit: number; + charts: TopNSubchart[]; + showFrames: boolean; +} + +export const ChartGrid: React.FC = ({ limit, charts, showFrames }) => { + const maximum = Math.min(limit, charts.length ?? 0); + + const ncharts = Math.min(maximum, charts.length); + + const [selectedSubchart, setSelectedSubchart] = useState(undefined); + + return ( + <> + + +

Top {charts.length}

+
+ + + {take(charts, ncharts).map((subchart, i) => ( + + + { + setSelectedSubchart(subchart); + }} + showFrames={showFrames} + padTitle + /> + + + ))} + + {selectedSubchart && ( + { + setSelectedSubchart(undefined); + }} + > + + + )} + + ); +}; diff --git a/x-pack/plugins/profiling/public/components/contexts/profiling_dependencies/profiling_dependencies_context.tsx b/x-pack/plugins/profiling/public/components/contexts/profiling_dependencies/profiling_dependencies_context.tsx new file mode 100644 index 0000000000000..240d34b8e18c7 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/contexts/profiling_dependencies/profiling_dependencies_context.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 { CoreStart, CoreSetup } from '@kbn/core/public'; +import { createContext } from 'react'; +import { Services } from '../../../services'; +import { ProfilingPluginPublicSetupDeps, ProfilingPluginPublicStartDeps } from '../../../types'; + +export interface ProfilingDependencies { + start: { + core: CoreStart; + } & ProfilingPluginPublicStartDeps; + setup: { + core: CoreSetup; + } & ProfilingPluginPublicSetupDeps; + services: Services; +} + +export const ProfilingDependenciesContext = createContext( + undefined +); + +export const ProfilingDependenciesContextProvider = ProfilingDependenciesContext.Provider; diff --git a/x-pack/plugins/profiling/public/components/contexts/profiling_dependencies/use_profiling_dependencies.tsx b/x-pack/plugins/profiling/public/components/contexts/profiling_dependencies/use_profiling_dependencies.tsx new file mode 100644 index 0000000000000..7e6ca9324c9f6 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/contexts/profiling_dependencies/use_profiling_dependencies.tsx @@ -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 { useContext } from 'react'; +import { ProfilingDependenciesContext } from './profiling_dependencies_context'; + +export function useProfilingDependencies() { + const context = useContext(ProfilingDependenciesContext); + if (!context) { + throw new Error('ProfilingDependenciesContext not found'); + } + return context; +} diff --git a/x-pack/plugins/profiling/public/components/contexts/route_breadcrumbs_context/index.tsx b/x-pack/plugins/profiling/public/components/contexts/route_breadcrumbs_context/index.tsx new file mode 100644 index 0000000000000..fff05f05a3713 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/contexts/route_breadcrumbs_context/index.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 { Route, RouteMatch, useMatchRoutes } from '@kbn/typed-react-router-config'; +import { ChromeBreadcrumb } from '@kbn/core/public'; +import { compact, isEqual } from 'lodash'; +import React, { createContext, useMemo, useState } from 'react'; +import { useBreadcrumbs } from '@kbn/observability-plugin/public'; + +export interface Breadcrumb { + title: string; + href: string; +} + +interface BreadcrumbApi { + set(route: Route, breadcrumb: Breadcrumb[]): void; + unset(route: Route): void; + getBreadcrumbs(matches: RouteMatch[]): Breadcrumb[]; +} + +export const RouteBreadcrumbsContext = createContext(undefined); + +export function RouteBreadcrumbsContextProvider({ children }: { children: React.ReactElement }) { + const [, forceUpdate] = useState({}); + + const breadcrumbs = useMemo(() => { + return new Map(); + }, []); + + const matches: RouteMatch[] = useMatchRoutes(); + + const api = useMemo( + () => ({ + set(route, breadcrumb) { + if (!isEqual(breadcrumbs.get(route), breadcrumb)) { + breadcrumbs.set(route, breadcrumb); + forceUpdate({}); + } + }, + unset(route) { + if (breadcrumbs.has(route)) { + breadcrumbs.delete(route); + forceUpdate({}); + } + }, + getBreadcrumbs(currentMatches: RouteMatch[]) { + return compact( + currentMatches.flatMap((match) => { + const breadcrumb = breadcrumbs.get(match.route); + + return breadcrumb; + }) + ); + }, + }), + [breadcrumbs] + ); + + const formattedBreadcrumbs: ChromeBreadcrumb[] = api + .getBreadcrumbs(matches) + .map((breadcrumb, index, array) => { + return { + text: breadcrumb.title, + ...(index === array.length - 1 + ? {} + : { + href: breadcrumb.href, + }), + }; + }); + + useBreadcrumbs(formattedBreadcrumbs); + + return ( + {children} + ); +} diff --git a/x-pack/plugins/profiling/public/components/contexts/route_breadcrumbs_context/use_route_breadcrumb.ts b/x-pack/plugins/profiling/public/components/contexts/route_breadcrumbs_context/use_route_breadcrumb.ts new file mode 100644 index 0000000000000..79a34e3ff79a9 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/contexts/route_breadcrumbs_context/use_route_breadcrumb.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 { useCurrentRoute } from '@kbn/typed-react-router-config'; +import { useContext, useEffect, useRef } from 'react'; +import { castArray } from 'lodash'; +import { RouteBreadcrumbsContext, Breadcrumb } from '.'; + +export function useRouteBreadcrumb(breadcrumb: Breadcrumb | Breadcrumb[]) { + const api = useContext(RouteBreadcrumbsContext); + + if (!api) { + throw new Error('Missing Breadcrumb API in context'); + } + + const { match } = useCurrentRoute(); + + const matchedRoute = useRef(match?.route); + + useEffect(() => { + if (matchedRoute.current && matchedRoute.current !== match?.route) { + api.unset(matchedRoute.current); + } + + matchedRoute.current = match?.route; + + if (matchedRoute.current) { + api.set(matchedRoute.current, castArray(breadcrumb)); + } + + return () => { + if (matchedRoute.current) { + api.unset(matchedRoute.current); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [matchedRoute.current, match?.route]); +} diff --git a/x-pack/plugins/profiling/public/components/contexts/time_range_context/index.tsx b/x-pack/plugins/profiling/public/components/contexts/time_range_context/index.tsx new file mode 100644 index 0000000000000..4bd38ea31a95a --- /dev/null +++ b/x-pack/plugins/profiling/public/components/contexts/time_range_context/index.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { uniqueId } from 'lodash'; +import React, { useMemo, useState } from 'react'; + +export const TimeRangeContext = React.createContext< + { timeRangeId: string; refresh: () => void } | undefined +>(undefined); + +export function TimeRangeContextProvider({ children }: { children: React.ReactElement }) { + const [timeRangeId, setTimeRangeId] = useState(uniqueId()); + + const timeRangeContextValue = useMemo(() => { + return { + timeRangeId, + refresh: () => { + setTimeRangeId(uniqueId()); + }, + }; + }, [timeRangeId]); + + return ( + {children} + ); +} diff --git a/x-pack/plugins/profiling/public/components/flame_graphs_view/flamegraph_information_window.tsx b/x-pack/plugins/profiling/public/components/flame_graphs_view/flamegraph_information_window.tsx new file mode 100644 index 0000000000000..824e6d1476a14 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/flame_graphs_view/flamegraph_information_window.tsx @@ -0,0 +1,184 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiLoadingSpinner, + EuiPanel, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { NOT_AVAILABLE_LABEL } from '../../../common'; +import { AsyncStatus } from '../../hooks/use_async'; +import { getImpactRows } from './get_impact_rows'; + +interface Props { + frame?: { + exeFileName: string; + functionName: string; + sourceFileName: string; + samples: number; + childSamples: number; + }; + sampledTraces: number; + totalTraces: number; + totalSeconds: number; + onClose: () => void; + status: AsyncStatus; +} + +function KeyValueList({ rows }: { rows: Array<{ label: string; value: React.ReactNode }> }) { + return ( + + {rows.map((row, index) => ( + <> + + + {row.label}: + + {row.value} + + + + {index < rows.length - 1 ? ( + + + + ) : undefined} + + ))} + + ); +} + +function FlamegraphFrameInformationPanel({ + children, + onClose, + status, +}: { + children: React.ReactNode; + onClose: () => void; + status: AsyncStatus; +}) { + return ( + + + + + + + + +

+ {i18n.translate('xpack.profiling.flameGraphInformationWindowTitle', { + defaultMessage: 'Frame information', + })} +

+
+
+ {status === AsyncStatus.Loading ? ( + + + + ) : undefined} +
+
+ + onClose()} /> + +
+
+ {children} +
+
+ ); +} + +export function FlamegraphInformationWindow({ + onClose, + frame, + sampledTraces, + totalTraces, + totalSeconds, + status, +}: Props) { + if (!frame) { + return ( + + + {i18n.translate('xpack.profiling.flamegraphInformationWindow.selectFrame', { + defaultMessage: 'Click on a frame to display more information', + })} + + + ); + } + + const { childSamples, exeFileName, samples, functionName, sourceFileName } = frame; + + const impactRows = getImpactRows({ + samples, + childSamples, + sampledTraces, + totalSeconds, + totalTraces, + }); + + return ( + + + + + + + + + +

+ {i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.impactEstimatesTitle', + { defaultMessage: 'Impact estimates' } + )} +

+
+
+ + + +
+
+
+
+ ); +} diff --git a/x-pack/plugins/profiling/public/components/flame_graphs_view/get_impact_rows.ts b/x-pack/plugins/profiling/public/components/flame_graphs_view/get_impact_rows.ts new file mode 100644 index 0000000000000..8ca1347e4497f --- /dev/null +++ b/x-pack/plugins/profiling/public/components/flame_graphs_view/get_impact_rows.ts @@ -0,0 +1,189 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { asCost } from '../../utils/formatters/as_cost'; +import { asDuration } from '../../utils/formatters/as_duration'; +import { asPercentage } from '../../utils/formatters/as_percentage'; +import { asWeight } from '../../utils/formatters/as_weight'; + +const ANNUAL_SECONDS = 60 * 60 * 24 * 365; + +// The assumed amortized per-core average power consumption. +const PER_CORE_WATT = 40; + +// The assumed CO2 emissions per KWH (sourced from www.eia.gov) +const CO2_PER_KWH = 0.92; + +// The cost of a CPU core per hour, in dollars +const CORE_COST_PER_HOUR = 0.0425; + +export function getImpactRows({ + samples, + childSamples, + sampledTraces, + totalTraces, + totalSeconds, +}: { + samples: number; + childSamples: number; + sampledTraces: number; + totalTraces: number; + totalSeconds: number; +}) { + const percentage = samples / sampledTraces; + const percentageNoChildren = (samples - childSamples) / sampledTraces; + const totalCoreSeconds = totalTraces / 20; + const coreSeconds = totalCoreSeconds * percentage; + const coreSecondsNoChildren = totalCoreSeconds * percentageNoChildren; + const coreHours = coreSeconds / (60 * 60); + const coreHoursNoChildren = coreSecondsNoChildren / (60 * 60); + const annualizedScaleUp = ANNUAL_SECONDS / totalSeconds; + const co2 = ((PER_CORE_WATT * coreHours) / 1000.0) * CO2_PER_KWH; + const co2NoChildren = ((PER_CORE_WATT * coreHoursNoChildren) / 1000.0) * CO2_PER_KWH; + const annualizedCo2 = co2 * annualizedScaleUp; + const annualizedCo2NoChildren = co2NoChildren * annualizedScaleUp; + const dollarCost = coreHours * CORE_COST_PER_HOUR; + const dollarCostNoChildren = coreHoursNoChildren * CORE_COST_PER_HOUR; + + const impactRows = [ + { + label: i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.percentageCpuTimeInclusiveLabel', + { + defaultMessage: '% of CPU time', + } + ), + value: asPercentage(percentage), + }, + { + label: i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.percentageCpuTimeExclusiveLabel', + { + defaultMessage: '% of CPU time (excl. children)', + } + ), + value: asPercentage(percentageNoChildren), + }, + { + label: i18n.translate('xpack.profiling.flameGraphInformationWindow.samplesLabel', { + defaultMessage: 'Samples', + }), + value: samples, + }, + { + label: i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.coreSecondsInclusiveLabel', + { + defaultMessage: 'Core-seconds', + } + ), + value: asDuration(coreSeconds), + }, + { + label: i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.coreSecondsExclusiveLabel', + { + defaultMessage: 'Core-seconds (excl. children)', + } + ), + value: asDuration(coreSecondsNoChildren), + }, + { + label: i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.annualizedCoreSecondsInclusiveLabel', + { + defaultMessage: 'Annualized core-seconds', + } + ), + value: asDuration(coreSeconds * annualizedScaleUp), + }, + { + label: i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.annualizedCoreSecondsExclusiveLabel', + { + defaultMessage: 'Annualized core-seconds (excl. children)', + } + ), + value: asDuration(coreSecondsNoChildren * annualizedScaleUp), + }, + { + label: i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.co2EmissionInclusiveLabel', + { + defaultMessage: 'CO2 emission', + } + ), + value: asWeight(co2), + }, + { + label: i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.co2EmissionExclusiveLabel', + { + defaultMessage: 'CO2 emission (excl. children)', + } + ), + value: asWeight(co2NoChildren), + }, + { + label: i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.annualizedCo2InclusiveLabel', + { + defaultMessage: 'Annualized CO2', + } + ), + value: asWeight(annualizedCo2), + }, + { + label: i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.annualizedCo2ExclusiveLabel', + { + defaultMessage: 'Annualized CO2 (excl. children)', + } + ), + value: asWeight(annualizedCo2NoChildren), + }, + { + label: i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.dollarCostInclusiveLabel', + { + defaultMessage: 'Dollar cost', + } + ), + value: asCost(dollarCost), + }, + { + label: i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.dollarCostExclusiveLabel', + { + defaultMessage: 'Dollar cost (excl. children)', + } + ), + value: asCost(dollarCostNoChildren), + }, + { + label: i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.annualizedDollarCostInclusiveLabel', + { + defaultMessage: 'Annualized dollar cost', + } + ), + value: asCost(dollarCost * annualizedScaleUp), + }, + { + label: i18n.translate( + 'xpack.profiling.flameGraphInformationWindow.annualizedDollarCostExclusiveLabel', + { + defaultMessage: 'Annualized dollar cost (excl. children)', + } + ), + value: asCost(dollarCostNoChildren * annualizedScaleUp), + }, + ]; + + return impactRows; +} diff --git a/x-pack/plugins/profiling/public/components/flame_graphs_view/index.tsx b/x-pack/plugins/profiling/public/components/flame_graphs_view/index.tsx new file mode 100644 index 0000000000000..6a8802599f6cc --- /dev/null +++ b/x-pack/plugins/profiling/public/components/flame_graphs_view/index.tsx @@ -0,0 +1,187 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiPageHeaderContentProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { FlameGraphComparisonMode } from '../../../common/flamegraph'; +import { useProfilingParams } from '../../hooks/use_profiling_params'; +import { useProfilingRouter } from '../../hooks/use_profiling_router'; +import { useProfilingRoutePath } from '../../hooks/use_profiling_route_path'; +import { useTimeRange } from '../../hooks/use_time_range'; +import { useTimeRangeAsync } from '../../hooks/use_time_range_async'; +import { AsyncComponent } from '../async_component'; +import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies'; +import { FlameGraph } from '../flamegraph'; +import { PrimaryAndComparisonSearchBar } from '../primary_and_comparison_search_bar'; +import { ProfilingAppPageTemplate } from '../profiling_app_page_template'; +import { RedirectTo } from '../redirect_to'; + +export function FlameGraphsView({ children }: { children: React.ReactElement }) { + const { + path, + query, + query: { rangeFrom, rangeTo, kuery }, + } = useProfilingParams('/flamegraphs/*'); + + const timeRange = useTimeRange({ rangeFrom, rangeTo }); + + const comparisonTimeRange = useTimeRange( + 'comparisonRangeFrom' in query + ? { rangeFrom: query.comparisonRangeFrom, rangeTo: query.comparisonRangeTo, optional: true } + : { rangeFrom: undefined, rangeTo: undefined, optional: true } + ); + + const comparisonKuery = 'comparisonKuery' in query ? query.comparisonKuery : ''; + const comparisonMode = + 'comparisonMode' in query ? query.comparisonMode : FlameGraphComparisonMode.Absolute; + + const { + services: { fetchElasticFlamechart }, + } = useProfilingDependencies(); + + const state = useTimeRangeAsync(() => { + return Promise.all([ + fetchElasticFlamechart({ + timeFrom: new Date(timeRange.start).getTime() / 1000, + timeTo: new Date(timeRange.end).getTime() / 1000, + kuery, + }), + comparisonTimeRange.start && comparisonTimeRange.end + ? fetchElasticFlamechart({ + timeFrom: new Date(comparisonTimeRange.start).getTime() / 1000, + timeTo: new Date(comparisonTimeRange.end).getTime() / 1000, + kuery: comparisonKuery, + }) + : Promise.resolve(undefined), + ]).then(([primaryFlamegraph, comparisonFlamegraph]) => { + return { + primaryFlamegraph, + comparisonFlamegraph, + }; + }); + }, [ + timeRange.start, + timeRange.end, + kuery, + comparisonTimeRange.start, + comparisonTimeRange.end, + comparisonKuery, + fetchElasticFlamechart, + ]); + + const { data } = state; + + const routePath = useProfilingRoutePath(); + + const profilingRouter = useProfilingRouter(); + + const isDifferentialView = routePath === '/flamegraphs/differential'; + + const tabs: Required['tabs'] = [ + { + label: i18n.translate('xpack.profiling.flameGraphsView.flameGraphTabLabel', { + defaultMessage: 'Flamegraph', + }), + isSelected: !isDifferentialView, + href: profilingRouter.link('/flamegraphs/flamegraph', { query }), + }, + { + label: i18n.translate('xpack.profiling.flameGraphsView.differentialFlameGraphTabLabel', { + defaultMessage: 'Differential flamegraph', + }), + isSelected: isDifferentialView, + href: profilingRouter.link('/flamegraphs/differential', { + query: { + ...query, + comparisonRangeFrom: query.rangeFrom, + comparisonRangeTo: query.rangeTo, + comparisonKuery: query.kuery, + comparisonMode, + }, + }), + }, + ]; + + if (routePath === '/flamegraphs') { + return ; + } + + return ( + + + {isDifferentialView ? ( + + + + + + + { + if (!('comparisonRangeFrom' in query)) { + return; + } + + profilingRouter.push(routePath, { + path, + query: { + ...query, + comparisonMode: nextComparisonMode as FlameGraphComparisonMode, + }, + }); + }} + options={[ + { + id: FlameGraphComparisonMode.Absolute, + label: i18n.translate( + 'xpack.profiling.flameGraphsView.differentialFlameGraphComparisonModeAbsoluteButtonLabel', + { + defaultMessage: 'Abs', + } + ), + }, + { + id: FlameGraphComparisonMode.Relative, + label: i18n.translate( + 'xpack.profiling.flameGraphsView.differentialFlameGraphComparisonModeRelativeButtonLabel', + { + defaultMessage: 'Rel', + } + ), + }, + ]} + /> + + + + ) : null} + + + + + {children} + + + + ); +} diff --git a/x-pack/plugins/profiling/public/components/flamegraph.tsx b/x-pack/plugins/profiling/public/components/flamegraph.tsx new file mode 100644 index 0000000000000..afc241394aafb --- /dev/null +++ b/x-pack/plugins/profiling/public/components/flamegraph.tsx @@ -0,0 +1,330 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { Chart, Datum, Flame, FlameLayerValue, PartialTheme, Settings } from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSwitch, useEuiTheme } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Maybe } from '@kbn/observability-plugin/common/typings'; +import { isNumber } from 'lodash'; +import React, { useEffect, useMemo, useState } from 'react'; +import { ElasticFlameGraph, FlameGraphComparisonMode } from '../../common/flamegraph'; +import { useAsync } from '../hooks/use_async'; +import { asPercentage } from '../utils/formatters/as_percentage'; +import { getFlamegraphModel } from '../utils/get_flamegraph_model'; +import { useProfilingDependencies } from './contexts/profiling_dependencies/use_profiling_dependencies'; +import { FlamegraphInformationWindow } from './flame_graphs_view/flamegraph_information_window'; + +function TooltipRow({ + value, + label, + comparison, + formatAsPercentage, + showChange, +}: { + value: number; + label: string; + comparison?: number; + formatAsPercentage: boolean; + showChange: boolean; +}) { + const valueLabel = formatAsPercentage ? asPercentage(value, 2) : value.toString(); + const comparisonLabel = + formatAsPercentage && isNumber(comparison) + ? asPercentage(comparison, 2) + : comparison?.toString(); + + const diff = showChange && isNumber(comparison) ? comparison - value : undefined; + + let diffLabel: string | undefined = diff?.toString(); + + if (diff === 0) { + diffLabel = i18n.translate('xpack.profiling.flameGraphToolTip.diffNoChange', { + defaultMessage: 'no change', + }); + } else if (formatAsPercentage && diff !== undefined) { + diffLabel = asPercentage(diff, 2); + } + + return ( + + + + {label} + + + {comparison + ? i18n.translate('xpack.profiling.flameGraphTooltip.valueLabel', { + defaultMessage: `{value} vs {comparison}`, + values: { + value: valueLabel, + comparison: comparisonLabel, + }, + }) + : valueLabel} + {diffLabel ? ` (${diffLabel})` : ''} + + + + ); +} + +function FlameGraphTooltip({ + label, + countInclusive, + countExclusive, + samples, + totalSamples, + comparisonCountInclusive, + comparisonCountExclusive, + comparisonSamples, + comparisonTotalSamples, +}: { + samples: number; + label: string; + countInclusive: number; + countExclusive: number; + totalSamples: number; + comparisonCountInclusive?: number; + comparisonCountExclusive?: number; + comparisonSamples?: number; + comparisonTotalSamples?: number; +}) { + return ( + + + {label} + + + + + + + + + + ); +} + +export interface FlameGraphProps { + id: string; + height: number | string; + comparisonMode: FlameGraphComparisonMode; + primaryFlamegraph?: ElasticFlameGraph; + comparisonFlamegraph?: ElasticFlameGraph; +} + +export const FlameGraph: React.FC = ({ + id, + height, + comparisonMode, + primaryFlamegraph, + comparisonFlamegraph, +}) => { + const theme = useEuiTheme(); + + const { + services: { fetchFrameInformation }, + } = useProfilingDependencies(); + + const columnarData = useMemo(() => { + return getFlamegraphModel({ + primaryFlamegraph, + comparisonFlamegraph, + colorSuccess: theme.euiTheme.colors.success, + colorDanger: theme.euiTheme.colors.danger, + colorNeutral: theme.euiTheme.colors.lightShade, + comparisonMode, + }); + }, [ + primaryFlamegraph, + comparisonFlamegraph, + theme.euiTheme.colors.success, + theme.euiTheme.colors.danger, + theme.euiTheme.colors.lightShade, + comparisonMode, + ]); + + const chartTheme: PartialTheme = { + chartMargins: { top: 0, left: 0, bottom: 0, right: 0 }, + chartPaddings: { left: 0, right: 0, top: 0, bottom: 0 }, + }; + + const totalSamples = columnarData.viewModel.value[0]; + + const [highlightedVmIndex, setHighlightedVmIndex] = useState(undefined); + + const highlightedFrameQueryParams = useMemo(() => { + if (!primaryFlamegraph || highlightedVmIndex === undefined || highlightedVmIndex === 0) { + return undefined; + } + + const frameID = primaryFlamegraph.FrameID[highlightedVmIndex]; + const executableID = primaryFlamegraph.ExecutableID[highlightedVmIndex]; + + return { + frameID, + executableID, + }; + }, [primaryFlamegraph, highlightedVmIndex]); + + const { data: highlightedFrame, status: highlightedFrameStatus } = useAsync(() => { + if (!highlightedFrameQueryParams) { + return Promise.resolve(undefined); + } + + return fetchFrameInformation({ + frameID: highlightedFrameQueryParams.frameID, + executableID: highlightedFrameQueryParams.executableID, + }); + }, [highlightedFrameQueryParams, fetchFrameInformation]); + + const selected: undefined | React.ComponentProps['frame'] = + primaryFlamegraph && highlightedFrame && highlightedVmIndex !== undefined + ? { + exeFileName: highlightedFrame.ExeFileName, + sourceFileName: highlightedFrame.SourceFilename, + functionName: highlightedFrame.FunctionName, + samples: primaryFlamegraph.Value[highlightedVmIndex], + childSamples: + primaryFlamegraph.Value[highlightedVmIndex] - + primaryFlamegraph.CountExclusive[highlightedVmIndex], + } + : undefined; + + useEffect(() => { + setHighlightedVmIndex(undefined); + }, [columnarData.key]); + + const [showInformationWindow, setShowInformationWindow] = useState(false); + + return ( + + + { + setShowInformationWindow((prev) => !prev); + }} + label={i18n.translate('xpack.profiling.flameGraph.showInformationWindow', { + defaultMessage: 'Show information window', + })} + /> + + + + {columnarData.viewModel.label.length > 0 && ( + + + { + const selectedElement = elements[0] as Maybe; + if (Number.isNaN(selectedElement?.vmIndex)) { + setHighlightedVmIndex(undefined); + } else { + setHighlightedVmIndex(selectedElement!.vmIndex); + } + }} + tooltip={{ + customTooltip: (props) => { + if (!primaryFlamegraph) { + return <>; + } + + const valueIndex = props.values[0].valueAccessor as number; + const label = primaryFlamegraph.Label[valueIndex]; + const samples = primaryFlamegraph.Value[valueIndex]; + const countInclusive = primaryFlamegraph.CountInclusive[valueIndex]; + const countExclusive = primaryFlamegraph.CountExclusive[valueIndex]; + const nodeID = primaryFlamegraph.ID[valueIndex]; + + const comparisonNode = columnarData.comparisonNodesById[nodeID]; + + return ( + + ); + }, + }} + /> + d.value as number} + valueFormatter={(value) => `${value}`} + animation={{ duration: 100 }} + controlProviderCallback={{}} + /> + + + )} + {showInformationWindow ? ( + + { + setShowInformationWindow(false); + }} + /> + + ) : undefined} + + + + ); +}; diff --git a/x-pack/plugins/profiling/public/components/functions_view/index.tsx b/x-pack/plugins/profiling/public/components/functions_view/index.tsx new file mode 100644 index 0000000000000..94410f961c469 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/functions_view/index.tsx @@ -0,0 +1,167 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiFlexGroup, EuiFlexItem, EuiPageHeaderContentProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { TypeOf } from '@kbn/typed-react-router-config'; +import React from 'react'; +import { useProfilingParams } from '../../hooks/use_profiling_params'; +import { useProfilingRouter } from '../../hooks/use_profiling_router'; +import { useProfilingRoutePath } from '../../hooks/use_profiling_route_path'; +import { useTimeRange } from '../../hooks/use_time_range'; +import { useTimeRangeAsync } from '../../hooks/use_time_range_async'; +import { ProfilingRoutes } from '../../routing'; +import { AsyncComponent } from '../async_component'; +import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies'; +import { PrimaryAndComparisonSearchBar } from '../primary_and_comparison_search_bar'; +import { ProfilingAppPageTemplate } from '../profiling_app_page_template'; +import { RedirectTo } from '../redirect_to'; +import { TopNFunctionsTable } from '../topn_functions'; + +export function FunctionsView({ children }: { children: React.ReactElement }) { + const { + path, + query, + query: { rangeFrom, rangeTo, kuery, sortDirection, sortField }, + } = useProfilingParams('/functions/*'); + + const timeRange = useTimeRange({ rangeFrom, rangeTo }); + + const comparisonTimeRange = useTimeRange( + 'comparisonRangeFrom' in query + ? { rangeFrom: query.comparisonRangeFrom, rangeTo: query.comparisonRangeTo, optional: true } + : { rangeFrom: undefined, rangeTo: undefined, optional: true } + ); + + const comparisonKuery = 'comparisonKuery' in query ? query.comparisonKuery : ''; + + const { + services: { fetchTopNFunctions }, + } = useProfilingDependencies(); + + const state = useTimeRangeAsync(() => { + return fetchTopNFunctions({ + timeFrom: new Date(timeRange.start).getTime() / 1000, + timeTo: new Date(timeRange.end).getTime() / 1000, + startIndex: 0, + endIndex: 1000, + kuery, + }); + }, [timeRange.start, timeRange.end, kuery, fetchTopNFunctions]); + + const comparisonState = useTimeRangeAsync(() => { + if (!comparisonTimeRange.start || !comparisonTimeRange.end) { + return undefined; + } + return fetchTopNFunctions({ + timeFrom: new Date(comparisonTimeRange.start).getTime() / 1000, + timeTo: new Date(comparisonTimeRange.end).getTime() / 1000, + startIndex: 0, + endIndex: 1000, + kuery: comparisonKuery, + }); + }, [comparisonTimeRange.start, comparisonTimeRange.end, comparisonKuery, fetchTopNFunctions]); + + const routePath = useProfilingRoutePath() as + | '/functions' + | '/functions/topn' + | '/functions/differential'; + + const profilingRouter = useProfilingRouter(); + + const isDifferentialView = routePath === '/functions/differential'; + + const tabs: Required['tabs'] = [ + { + label: i18n.translate('xpack.profiling.functionsView.functionsTabLabel', { + defaultMessage: 'TopN functions', + }), + isSelected: !isDifferentialView, + href: profilingRouter.link('/functions/topn', { query }), + }, + { + label: i18n.translate('xpack.profiling.functionsView.differentialFunctionsTabLabel', { + defaultMessage: 'Differential TopN functions', + }), + isSelected: isDifferentialView, + href: profilingRouter.link('/functions/differential', { + query: { + ...query, + comparisonRangeFrom: query.rangeFrom, + comparisonRangeTo: query.rangeTo, + comparisonKuery: query.kuery, + }, + }), + }, + ]; + + if (routePath === '/functions') { + return ; + } + + return ( + + <> + + {isDifferentialView ? ( + + + + ) : null} + + + + + { + profilingRouter.push(routePath, { + path, + query: { + ...query, + sortField: nextSort.sortField, + sortDirection: nextSort.sortDirection, + }, + }); + }} + /> + + + {isDifferentialView && comparisonTimeRange.start && comparisonTimeRange.end ? ( + + + { + profilingRouter.push(routePath, { + path, + query: { + ...(query as TypeOf< + ProfilingRoutes, + '/functions/differential' + >['query']), + sortField: nextSort.sortField, + sortDirection: nextSort.sortDirection, + }, + }); + }} + topNFunctions={comparisonState.data} + comparisonTopNFunctions={state.data} + /> + + + ) : null} + + + + {children} + + + ); +} diff --git a/x-pack/plugins/profiling/public/components/primary_and_comparison_search_bar.tsx b/x-pack/plugins/profiling/public/components/primary_and_comparison_search_bar.tsx new file mode 100644 index 0000000000000..7a8a09e845589 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/primary_and_comparison_search_bar.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { TypeOf } from '@kbn/typed-react-router-config'; +import React from 'react'; +import { useAnyOfProfilingParams } from '../hooks/use_profiling_params'; +import { useProfilingRouter } from '../hooks/use_profiling_router'; +import { useProfilingRoutePath } from '../hooks/use_profiling_route_path'; +import { useTimeRangeContext } from '../hooks/use_time_range_context'; +import { ProfilingRoutes } from '../routing'; +import { PrimaryProfilingSearchBar } from './profiling_app_page_template/primary_profiling_search_bar'; +import { ProfilingSearchBar } from './profiling_app_page_template/profiling_search_bar'; + +export function PrimaryAndComparisonSearchBar() { + const { + path, + query, + query: { comparisonKuery, comparisonRangeFrom, comparisonRangeTo }, + } = useAnyOfProfilingParams('/flamegraphs/differential', '/functions/differential'); + + const { refresh } = useTimeRangeContext(); + + const profilingRouter = useProfilingRouter(); + const routePath = useProfilingRoutePath() as + | '/flamegraphs/differential' + | '/functions/differential'; + + function navigate(nextOptions: { rangeFrom: string; rangeTo: string; kuery?: string }) { + if (routePath === '/flamegraphs/differential') { + profilingRouter.push(routePath, { + path, + query: { + ...(query as TypeOf['query']), + comparisonRangeFrom: nextOptions.rangeFrom, + comparisonRangeTo: nextOptions.rangeTo, + comparisonKuery: nextOptions.kuery ?? query.comparisonKuery, + }, + }); + } else { + profilingRouter.push(routePath, { + path, + query: { + ...(query as TypeOf['query']), + comparisonRangeFrom: nextOptions.rangeFrom, + comparisonRangeTo: nextOptions.rangeTo, + comparisonKuery: nextOptions.kuery ?? query.comparisonKuery, + }, + }); + } + } + + return ( + + + + + + { + navigate({ + kuery: String(next.query?.query || ''), + rangeFrom: next.dateRange.from, + rangeTo: next.dateRange.to, + }); + }} + onRefresh={(nextDateRange) => { + navigate({ + rangeFrom: nextDateRange.dateRange.from, + rangeTo: nextDateRange.dateRange.to, + }); + }} + onRefreshClick={() => { + refresh(); + }} + /> + + + ); +} diff --git a/x-pack/plugins/profiling/public/components/profiling_app_page_template/index.tsx b/x-pack/plugins/profiling/public/components/profiling_app_page_template/index.tsx new file mode 100644 index 0000000000000..03a04b6a3a8e0 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/profiling_app_page_template/index.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiPageHeaderContentProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; +import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies'; +import { PrimaryProfilingSearchBar } from './primary_profiling_search_bar'; + +export function ProfilingAppPageTemplate({ + children, + tabs, + hideSearchBar = false, + fullHeight = false, +}: { + children: React.ReactElement; + tabs: EuiPageHeaderContentProps['tabs']; + hideSearchBar?: boolean; + fullHeight?: boolean; +}) { + const { + start: { observability }, + } = useProfilingDependencies(); + + const { PageTemplate: ObservabilityPageTemplate } = observability.navigation; + + const history = useHistory(); + + useEffect(() => { + window.scrollTo(0, 0); + }, [history.location.pathname]); + + return ( + + + {!hideSearchBar && ( + + + + )} + {children} + + + ); +} diff --git a/x-pack/plugins/profiling/public/components/profiling_app_page_template/primary_profiling_search_bar.tsx b/x-pack/plugins/profiling/public/components/profiling_app_page_template/primary_profiling_search_bar.tsx new file mode 100644 index 0000000000000..fb4ee5d780f65 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/profiling_app_page_template/primary_profiling_search_bar.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 React, { useEffect } from 'react'; +import { useProfilingParams } from '../../hooks/use_profiling_params'; +import { useProfilingRouter } from '../../hooks/use_profiling_router'; +import { useProfilingRoutePath } from '../../hooks/use_profiling_route_path'; +import { useTimeRangeContext } from '../../hooks/use_time_range_context'; +import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies'; +import { ProfilingSearchBar } from './profiling_search_bar'; + +export function PrimaryProfilingSearchBar({ showSubmitButton }: { showSubmitButton?: boolean }) { + const { + start: { data }, + } = useProfilingDependencies(); + + const profilingRouter = useProfilingRouter(); + const routePath = useProfilingRoutePath(); + + const { + path, + query, + query: { rangeFrom, rangeTo, kuery }, + } = useProfilingParams('/*'); + + const { refresh } = useTimeRangeContext(); + + useEffect(() => { + // set time if both to and from are given in the url + if (rangeFrom && rangeTo) { + data.query.timefilter.timefilter.setTime({ + from: rangeFrom, + to: rangeTo, + }); + return; + } + }, [rangeFrom, rangeTo, data]); + + return ( + { + profilingRouter.push(routePath, { + path, + query: { + ...query, + kuery: String(next.query?.query || ''), + rangeFrom: next.dateRange.from, + rangeTo: next.dateRange.to, + }, + }); + }} + onRefresh={(nextDateRange) => { + profilingRouter.push(routePath, { + path, + query: { + ...query, + rangeFrom: nextDateRange.dateRange.from, + rangeTo: nextDateRange.dateRange.to, + }, + }); + }} + onRefreshClick={() => { + refresh(); + }} + showSubmitButton={showSubmitButton} + /> + ); +} diff --git a/x-pack/plugins/profiling/public/components/profiling_app_page_template/profiling_search_bar.tsx b/x-pack/plugins/profiling/public/components/profiling_app_page_template/profiling_search_bar.tsx new file mode 100644 index 0000000000000..de20f0f3211e0 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/profiling_app_page_template/profiling_search_bar.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { SearchBar } from '@kbn/unified-search-plugin/public'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { compact } from 'lodash'; +import { Query, TimeRange } from '@kbn/es-query'; +import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies'; +import { INDEX_EVENTS } from '../../../common'; + +export function ProfilingSearchBar({ + kuery, + rangeFrom, + rangeTo, + onQuerySubmit, + onRefresh, + onRefreshClick, + showSubmitButton = true, +}: { + kuery: string; + rangeFrom: string; + rangeTo: string; + onQuerySubmit: ( + payload: { + dateRange: TimeRange; + query?: Query; + }, + isUpdate?: boolean + ) => void; + onRefresh: Required>['onRefresh']; + onRefreshClick: () => void; + showSubmitButton?: boolean; +}) { + const { + start: { dataViews }, + } = useProfilingDependencies(); + + const [dataView, setDataView] = useState(); + + useEffect(() => { + dataViews + .create({ + title: INDEX_EVENTS, + }) + .then((nextDataView) => setDataView(nextDataView)); + }, [dataViews]); + + const searchBarQuery: Required>['query'] = { + language: 'kuery', + query: kuery, + }; + + return ( + + onQuerySubmit={({ dateRange, query }) => { + if (dateRange.from === rangeFrom && dateRange.to === rangeTo && query?.query === kuery) { + onRefreshClick(); + return; + } + + onQuerySubmit({ dateRange, query }); + }} + showQueryBar + showQueryInput + showDatePicker + showFilterBar={false} + showSaveQuery={false} + // showSubmitButton={showSubmitButton} + showSubmitButton={true} + query={searchBarQuery} + dateRangeFrom={rangeFrom} + dateRangeTo={rangeTo} + indexPatterns={compact([dataView])} + onRefresh={onRefresh} + /> + ); +} diff --git a/x-pack/plugins/profiling/public/components/redirect_to.tsx b/x-pack/plugins/profiling/public/components/redirect_to.tsx new file mode 100644 index 0000000000000..fc5020909289c --- /dev/null +++ b/x-pack/plugins/profiling/public/components/redirect_to.tsx @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { useHistory, Redirect } from 'react-router-dom'; + +export function RedirectTo({ pathname }: { pathname: string }) { + const { location } = useHistory(); + + return ; +} diff --git a/x-pack/plugins/profiling/public/components/redirect_with_default_date_range.tsx b/x-pack/plugins/profiling/public/components/redirect_with_default_date_range.tsx new file mode 100644 index 0000000000000..6a39aad78c6e4 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/redirect_with_default_date_range.tsx @@ -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 React from 'react'; +import { useDateRangeRedirect } from '../hooks/use_default_date_range_redirect'; + +export function RedirectWithDefaultDateRange({ children }: { children: React.ReactElement }) { + const { redirect, isDateRangeSet } = useDateRangeRedirect(); + + if (isDateRangeSet) { + return children; + } + + redirect(); + + return null; +} diff --git a/x-pack/plugins/profiling/public/components/route_breadcrumb.tsx b/x-pack/plugins/profiling/public/components/route_breadcrumb.tsx new file mode 100644 index 0000000000000..78d358d8a7b65 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/route_breadcrumb.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { useProfilingDependencies } from './contexts/profiling_dependencies/use_profiling_dependencies'; +import { useRouteBreadcrumb } from './contexts/route_breadcrumbs_context/use_route_breadcrumb'; + +export const RouteBreadcrumb = ({ + title, + href, + children, +}: { + title: string; + href: string; + children: React.ReactElement; +}) => { + const { + start: { core }, + } = useProfilingDependencies(); + useRouteBreadcrumb({ title, href: core.http.basePath.prepend('/app/profiling/' + href) }); + + return children; +}; diff --git a/x-pack/plugins/profiling/public/components/stack_frame_summary.tsx b/x-pack/plugins/profiling/public/components/stack_frame_summary.tsx new file mode 100644 index 0000000000000..9f25b4d9d8900 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/stack_frame_summary.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 { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import React from 'react'; +import { getCalleeFunction, getCalleeSource, StackFrameMetadata } from '../../common/profiling'; + +export function StackFrameSummary({ frame }: { frame: StackFrameMetadata }) { + return ( + + +
+ + {getCalleeFunction(frame)} + +
+
+ + {getCalleeSource(frame) || '‎'} + +
+ ); +} diff --git a/x-pack/plugins/profiling/public/components/stack_traces_view/get_stack_traces_tabs.ts b/x-pack/plugins/profiling/public/components/stack_traces_view/get_stack_traces_tabs.ts new file mode 100644 index 0000000000000..435333ce68987 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/stack_traces_view/get_stack_traces_tabs.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 { EuiPageHeaderContentProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { TypeOf } from '@kbn/typed-react-router-config'; +import { TopNType } from '../../../common/stack_traces'; +import { StatefulProfilingRouter } from '../../hooks/use_profiling_router'; +import { ProfilingRoutes } from '../../routing'; + +export function getStackTracesTabs({ + path, + query, + profilingRouter, +}: TypeOf & { + profilingRouter: StatefulProfilingRouter; +}): Required['tabs'] { + return [ + { + label: i18n.translate('xpack.profiling.stackTracesView.containersTabLabel', { + defaultMessage: 'Containers', + }), + topNType: TopNType.Containers, + }, + { + label: i18n.translate('xpack.profiling.stackTracesView.deploymentsTabLabel', { + defaultMessage: 'Deployments', + }), + topNType: TopNType.Deployments, + }, + { + label: i18n.translate('xpack.profiling.stackTracesView.threadsTabLabel', { + defaultMessage: 'Threads', + }), + topNType: TopNType.Threads, + }, + { + label: i18n.translate('xpack.profiling.stackTracesView.hostsTabLabel', { + defaultMessage: 'Hosts', + }), + topNType: TopNType.Hosts, + }, + { + label: i18n.translate('xpack.profiling.stackTracesView.tracesTabLabel', { + defaultMessage: 'Traces', + }), + topNType: TopNType.Traces, + }, + ].map((tab) => ({ + label: tab.label, + isSelected: tab.topNType === path.topNType, + href: profilingRouter.link(`/stacktraces/{topNType}`, { + path: { topNType: tab.topNType }, + query, + }), + })); +} diff --git a/x-pack/plugins/profiling/public/components/stack_traces_view/index.tsx b/x-pack/plugins/profiling/public/components/stack_traces_view/index.tsx new file mode 100644 index 0000000000000..23bb4a7bd4b6f --- /dev/null +++ b/x-pack/plugins/profiling/public/components/stack_traces_view/index.tsx @@ -0,0 +1,189 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { EuiButton, EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useState } from 'react'; +import { StackTracesDisplayOption, TopNType } from '../../../common/stack_traces'; +import { groupSamplesByCategory, TopNResponse, TopNSubchart } from '../../../common/topn'; +import { useProfilingParams } from '../../hooks/use_profiling_params'; +import { useProfilingRouter } from '../../hooks/use_profiling_router'; +import { useProfilingRoutePath } from '../../hooks/use_profiling_route_path'; +import { useTimeRange } from '../../hooks/use_time_range'; +import { useTimeRangeAsync } from '../../hooks/use_time_range_async'; +import { AsyncComponent } from '../async_component'; +import { ChartGrid } from '../chart_grid'; +import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies'; +import { ProfilingAppPageTemplate } from '../profiling_app_page_template'; +import { StackedBarChart } from '../stacked_bar_chart'; +import { getStackTracesTabs } from './get_stack_traces_tabs'; + +export function StackTracesView() { + const routePath = useProfilingRoutePath(); + + const profilingRouter = useProfilingRouter(); + + const { + path, + query, + path: { topNType }, + query: { rangeFrom, rangeTo, kuery, displayAs, limit: limitFromQueryParams }, + } = useProfilingParams('/stacktraces/{topNType}'); + + const limit = limitFromQueryParams || 10; + + const tabs = getStackTracesTabs({ + path, + query, + profilingRouter, + }); + + const { + services: { fetchTopN }, + } = useProfilingDependencies(); + + const timeRange = useTimeRange({ + rangeFrom, + rangeTo, + }); + + const state = useTimeRangeAsync(() => { + if (!topNType) { + return Promise.resolve({ charts: [], metadata: {} }); + } + return fetchTopN({ + type: topNType, + timeFrom: new Date(timeRange.start).getTime() / 1000, + timeTo: new Date(timeRange.end).getTime() / 1000, + kuery, + }).then((response: TopNResponse) => { + const totalCount = response.TotalCount; + const samples = response.TopN; + const charts = groupSamplesByCategory({ + samples, + totalCount, + metadata: response.Metadata, + labels: response.Labels, + }); + return { + charts, + }; + }); + }, [topNType, timeRange.start, timeRange.end, fetchTopN, kuery]); + + const [highlightedSubchart, setHighlightedSubchart] = useState( + undefined + ); + + const { data } = state; + + return ( + + + + + + + { + profilingRouter.push(routePath, { + path, + query: { + ...query, + displayAs: nextValue, + }, + }); + }} + options={[ + { + id: StackTracesDisplayOption.StackTraces, + iconType: 'visLine', + label: i18n.translate( + 'xpack.profiling.stackTracesView.stackTracesCountButton', + { + defaultMessage: 'Stack traces', + } + ), + }, + { + id: StackTracesDisplayOption.Percentage, + iconType: 'percent', + label: i18n.translate('xpack.profiling.stackTracesView.percentagesButton', { + defaultMessage: 'Percentages', + }), + }, + ]} + legend={i18n.translate('xpack.profiling.stackTracesView.displayOptionLegend', { + defaultMessage: 'Display option', + })} + /> + + + + { + profilingRouter.push(routePath, { + path, + query: { + ...query, + rangeFrom: nextRange.rangeFrom, + rangeTo: nextRange.rangeTo, + }, + }); + }} + onSampleClick={(sample) => { + setHighlightedSubchart( + data?.charts.find((subchart) => subchart.Category === sample.Category) + ); + }} + onSampleOut={() => { + setHighlightedSubchart(undefined); + }} + highlightedSubchart={highlightedSubchart} + showFrames={topNType === TopNType.Traces} + /> + + + + + + + + + + + {(data?.charts.length ?? 0) > limit ? ( + + { + profilingRouter.push(routePath, { + path, + query: { + ...query, + limit: limit + 10, + }, + }); + }} + > + {i18n.translate('xpack.profiling.stackTracesView.showMoreButton', { + defaultMessage: 'Show more', + })} + + + ) : null} + + + ); +} diff --git a/x-pack/plugins/profiling/public/components/stacked_bar_chart.tsx b/x-pack/plugins/profiling/public/components/stacked_bar_chart.tsx new file mode 100644 index 0000000000000..977a439d9ce9d --- /dev/null +++ b/x-pack/plugins/profiling/public/components/stacked_bar_chart.tsx @@ -0,0 +1,142 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + Axis, + BrushAxis, + Chart, + HistogramBarSeries, + ScaleType, + Settings, + StackMode, + timeFormatter, + Tooltip, + TooltipInfo, + XYChartElementEvent, +} from '@elastic/charts'; +import { EuiPanel } from '@elastic/eui'; +import React from 'react'; +import { TopNSample, TopNSubchart } from '../../common/topn'; +import { useKibanaTimeZoneSetting } from '../hooks/use_kibana_timezone_setting'; +import { useProfilingChartsTheme } from '../hooks/use_profiling_charts_theme'; +import { asPercentage } from '../utils/formatters/as_percentage'; +import { SubChart } from './subchart'; + +function SubchartTooltip({ + highlightedSubchart, + showFrames, +}: TooltipInfo & { highlightedSubchart: TopNSubchart; showFrames: boolean }) { + // max tooltip width - 2 * padding (16px) + const width = 224; + return ( + + + + ); +} + +export interface StackedBarChartProps { + height: number; + asPercentages: boolean; + onBrushEnd: (range: { rangeFrom: string; rangeTo: string }) => void; + onSampleClick: (sample: TopNSample) => void; + onSampleOut: () => void; + highlightedSubchart?: TopNSubchart; + charts: TopNSubchart[]; + showFrames: boolean; +} + +export const StackedBarChart: React.FC = ({ + height, + asPercentages, + onBrushEnd, + onSampleClick, + onSampleOut, + highlightedSubchart, + charts, + showFrames, +}) => { + const timeZone = useKibanaTimeZoneSetting(); + + const { chartsBaseTheme, chartsTheme } = useProfilingChartsTheme(); + + return ( + + { + const rangeFrom = new Date(brushEvent.x![0]).toISOString(); + const rangeTo = new Date(brushEvent.x![1]).toISOString(); + + onBrushEnd({ rangeFrom, rangeTo }); + }} + baseTheme={chartsBaseTheme} + theme={chartsTheme} + onElementClick={(events) => { + const [value] = events[0] as XYChartElementEvent; + onSampleClick(value.datum as TopNSample); + }} + onElementOver={() => { + onSampleOut(); + }} + onElementOut={() => { + onSampleOut(); + }} + /> + ( + + ) + : () => <> + } + /> + {charts.map((chart) => ( + + ))} + + (asPercentages ? asPercentage(d) : d.toFixed(0))} + /> + + ); +}; diff --git a/x-pack/plugins/profiling/public/components/subchart.tsx b/x-pack/plugins/profiling/public/components/subchart.tsx new file mode 100644 index 0000000000000..1021ba2036c8a --- /dev/null +++ b/x-pack/plugins/profiling/public/components/subchart.tsx @@ -0,0 +1,261 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { + AreaSeries, + Axis, + Chart, + CurveType, + ScaleType, + Settings, + timeFormatter, +} from '@elastic/charts'; +import { + EuiBadge, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiLink, + EuiSpacer, + EuiText, + useEuiTheme, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { StackFrameMetadata } from '../../common/profiling'; +import { getFieldNameForTopNType, TopNType } from '../../common/stack_traces'; +import { CountPerTime, OTHER_BUCKET_LABEL } from '../../common/topn'; +import { useKibanaTimeZoneSetting } from '../hooks/use_kibana_timezone_setting'; +import { useProfilingChartsTheme } from '../hooks/use_profiling_charts_theme'; +import { useProfilingParams } from '../hooks/use_profiling_params'; +import { useProfilingRouter } from '../hooks/use_profiling_router'; +import { asPercentage } from '../utils/formatters/as_percentage'; +import { StackFrameSummary } from './stack_frame_summary'; + +export interface SubChartProps { + index: number; + color: string; + height: number; + width?: number; + category: string; + label: string; + percentage: number; + data: CountPerTime[]; + showAxes: boolean; + metadata: StackFrameMetadata[]; + onShowMoreClick: (() => void) | null; + style?: React.ComponentProps['style']; + showFrames: boolean; + padTitle: boolean; +} + +const NUM_DISPLAYED_FRAMES = 5; + +export const SubChart: React.FC = ({ + index, + color, + category, + label, + percentage, + height, + data, + width, + showAxes, + metadata, + onShowMoreClick, + style, + showFrames, + padTitle, +}) => { + const theme = useEuiTheme(); + + const profilingRouter = useProfilingRouter(); + + const { path, query } = useProfilingParams('/stacktraces/{topNType}'); + + const href = profilingRouter.link('/stacktraces/{topNType}', { + path: { + topNType: TopNType.Traces, + }, + query: { + ...query, + kuery: `${getFieldNameForTopNType(path.topNType)}:"${category}"`, + }, + }); + + const timeZone = useKibanaTimeZoneSetting(); + + const { chartsTheme, chartsBaseTheme } = useProfilingChartsTheme(); + + const compact = !!onShowMoreClick; + + const displayedFrames = compact + ? metadata.concat().reverse().slice(0, NUM_DISPLAYED_FRAMES) + : metadata.concat().reverse(); + + const hasMoreFrames = displayedFrames.length < metadata.length; + + let bottomElement: React.ReactElement; + + if (metadata.length > 0) { + bottomElement = ( + <> + + + {displayedFrames.map((frame, frameIndex) => ( + <> + + + {metadata.indexOf(frame) + 1} + + + + + + {frameIndex < displayedFrames.length - 1 || hasMoreFrames ? ( + + + + ) : null} + + ))} + + + {hasMoreFrames && !!onShowMoreClick && ( + + {i18n.translate('xpack.profiling.stackTracesView.showMoreTracesButton', { + defaultMessage: 'Show more', + })} + + )} + + + ); + } else if (category === OTHER_BUCKET_LABEL && showFrames) { + bottomElement = ( + + + + {i18n.translate('xpack.profiling.stackTracesView.otherTraces', { + defaultMessage: '[This summarizes all traces that are too small to display]', + })} + + + + ); + } else { + bottomElement = ; + } + + return ( + + + + + + + {index} + + + + + {showFrames ? ( + onShowMoreClick?.()}> + {label} + + ) : ( + + {label} + + )} + + + {asPercentage(percentage / 100, 2)} + + + + + + + + {showAxes ? ( + + ) : null} + (showAxes ? Number(d).toFixed(0) : '')} + style={ + showAxes + ? {} + : { + tickLine: { visible: false }, + tickLabel: { visible: false }, + axisTitle: { visible: false }, + } + } + /> + + {!showAxes ? ( +
+ {i18n.translate('xpack.profiling.maxValue', { + defaultMessage: 'Max: {max}', + values: { max: Math.max(...data.map((value) => value.Count ?? 0)) }, + })} +
+ ) : null} +
+ {bottomElement} +
+ ); +}; diff --git a/x-pack/plugins/profiling/public/components/topn_functions.tsx b/x-pack/plugins/profiling/public/components/topn_functions.tsx new file mode 100644 index 0000000000000..3ad540983d903 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/topn_functions.tsx @@ -0,0 +1,251 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { + EuiBadge, + EuiBasicTable, + EuiBasicTableColumn, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiSpacer, + EuiText, + useEuiTheme, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { keyBy, orderBy } from 'lodash'; +import React, { useMemo } from 'react'; +import { TopNFunctions, TopNFunctionSortField } from '../../common/functions'; +import { getCalleeFunction, StackFrameMetadata } from '../../common/profiling'; +import { StackFrameSummary } from './stack_frame_summary'; + +interface Row { + rank: number; + frame: StackFrameMetadata; + samples: number; + exclusiveCPU: number; + inclusiveCPU: number; + diff?: { + rank: number; + exclusiveCPU: number; + inclusiveCPU: number; + }; +} + +function CPUStat({ cpu, diffCPU }: { cpu: number; diffCPU: number | undefined }) { + const cpuLabel = `${cpu.toFixed(2)}%`; + + if (diffCPU === undefined || diffCPU === 0) { + return <>{cpuLabel}; + } + const color = diffCPU < 0 ? 'success' : 'danger'; + const label = Math.abs(diffCPU) <= 0.01 ? '<0.01' : Math.abs(diffCPU).toFixed(2); + + return ( + + {cpuLabel} + + + ({label}) + + + + ); +} + +export const TopNFunctionsTable = ({ + sortDirection, + sortField, + onSortChange, + topNFunctions, + comparisonTopNFunctions, +}: { + sortDirection: 'asc' | 'desc'; + sortField: TopNFunctionSortField; + onSortChange: (options: { + sortDirection: 'asc' | 'desc'; + sortField: TopNFunctionSortField; + }) => void; + topNFunctions?: TopNFunctions; + comparisonTopNFunctions?: TopNFunctions; +}) => { + const totalCount: number = useMemo(() => { + if (!topNFunctions || !topNFunctions.TotalCount || topNFunctions.TotalCount === 0) { + return 0; + } + + return topNFunctions.TotalCount; + }, [topNFunctions]); + + const rows: Row[] = useMemo(() => { + if (!topNFunctions || !topNFunctions.TotalCount || topNFunctions.TotalCount === 0) { + return []; + } + + const comparisonDataById = comparisonTopNFunctions + ? keyBy(comparisonTopNFunctions.TopN, 'Id') + : {}; + + return topNFunctions.TopN.filter((topN) => topN.CountExclusive > 0).map((topN, i) => { + const comparisonRow = comparisonDataById?.[topN.Id]; + + const inclusiveCPU = (topN.CountInclusive / topNFunctions.TotalCount) * 100; + const exclusiveCPU = (topN.CountExclusive / topNFunctions.TotalCount) * 100; + + const diff = + comparisonTopNFunctions && comparisonRow + ? { + rank: topN.Rank - comparisonRow.Rank, + exclusiveCPU: + exclusiveCPU - + (comparisonRow.CountExclusive / comparisonTopNFunctions.TotalCount) * 100, + inclusiveCPU: + inclusiveCPU - + (comparisonRow.CountInclusive / comparisonTopNFunctions.TotalCount) * 100, + } + : undefined; + + return { + rank: topN.Rank, + frame: topN.Frame, + samples: topN.CountExclusive, + exclusiveCPU, + inclusiveCPU, + diff, + }; + }); + }, [topNFunctions, comparisonTopNFunctions]); + + const theme = useEuiTheme(); + + const columns: Array> = [ + { + field: TopNFunctionSortField.Rank, + name: i18n.translate('xpack.profiling.functionsView.rankColumnLabel', { + defaultMessage: 'Rank', + }), + align: 'right', + }, + { + field: TopNFunctionSortField.Frame, + name: i18n.translate('xpack.profiling.functionsView.functionColumnLabel', { + defaultMessage: 'Function', + }), + width: '100%', + render: (_, { frame }) => , + }, + { + field: TopNFunctionSortField.Samples, + name: i18n.translate('xpack.profiling.functionsView.samplesColumnLabel', { + defaultMessage: 'Samples', + }), + align: 'right', + }, + { + field: TopNFunctionSortField.ExclusiveCPU, + name: i18n.translate('xpack.profiling.functionsView.exclusiveCpuColumnLabel', { + defaultMessage: 'Exclusive CPU', + }), + render: (_, { exclusiveCPU, diff }) => { + return ; + }, + align: 'right', + }, + { + field: TopNFunctionSortField.InclusiveCPU, + name: i18n.translate('xpack.profiling.functionsView.inclusiveCpuColumnLabel', { + defaultMessage: 'Inclusive CPU', + }), + render: (_, { inclusiveCPU, diff }) => { + return ; + }, + align: 'right', + }, + ]; + + if (comparisonTopNFunctions) { + columns.push({ + field: TopNFunctionSortField.Diff, + name: i18n.translate('xpack.profiling.functionsView.diffColumnLabel', { + defaultMessage: 'Diff', + }), + align: 'right', + render: (_, { diff }) => { + if (!diff) { + return ( + + {i18n.translate('xpack.profiling.functionsView.newLabel', { defaultMessage: 'New' })} + + ); + } + + if (diff.rank === 0) { + return null; + } + + const color = diff.rank > 0 ? 'success' : 'danger'; + const icon = diff.rank > 0 ? 'sortDown' : 'sortUp'; + + return ( + + {diff.rank} + + ); + }, + }); + } + + const totalSampleCountLabel = i18n.translate( + 'xpack.profiling.functionsView.totalSampleCountLabel', + { + defaultMessage: 'Total sample count', + } + ); + + const sortedRows = orderBy( + rows, + (row) => { + return sortField === TopNFunctionSortField.Frame + ? getCalleeFunction(row.frame).toLowerCase() + : row[sortField]; + }, + [sortDirection] + ).slice(0, 100); + + return ( + <> + + {totalSampleCountLabel}: {totalCount} + + + + { + onSortChange({ + sortDirection: criteria.sort!.direction, + sortField: criteria.sort!.field as TopNFunctionSortField, + }); + }} + sorting={{ + enableAllColumns: true, + sort: { + direction: sortDirection, + field: sortField, + }, + }} + /> + + ); +}; diff --git a/x-pack/plugins/profiling/public/hooks/use_async.ts b/x-pack/plugins/profiling/public/hooks/use_async.ts new file mode 100644 index 0000000000000..ea6da578c3879 --- /dev/null +++ b/x-pack/plugins/profiling/public/hooks/use_async.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { HttpStart } from '@kbn/core-http-browser'; +import { useEffect, useState } from 'react'; +import { useProfilingDependencies } from '../components/contexts/profiling_dependencies/use_profiling_dependencies'; + +export enum AsyncStatus { + Loading = 'loading', + Init = 'init', + Settled = 'settled', +} + +export interface AsyncState { + data?: T; + error?: Error; + status: AsyncStatus; +} + +export type UseAsync = ( + fn: ({ http }: { http: HttpStart }) => Promise | undefined, + dependencies: any[] +) => AsyncState; + +export const useAsync: UseAsync = (fn, dependencies) => { + const { + start: { + core: { http }, + }, + } = useProfilingDependencies(); + const [asyncState, setAsyncState] = useState>({ + status: AsyncStatus.Init, + }); + + const { data, error } = asyncState; + + useEffect(() => { + const returnValue = fn({ http }); + + if (returnValue === undefined) { + setAsyncState({ + status: AsyncStatus.Init, + data: undefined, + error: undefined, + }); + return; + } + + setAsyncState({ + status: AsyncStatus.Loading, + data, + error, + }); + + returnValue.then((nextData) => { + setAsyncState({ + status: AsyncStatus.Settled, + data: nextData, + }); + }); + + returnValue.catch((nextError) => { + setAsyncState({ + status: AsyncStatus.Settled, + error: nextError, + }); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [http, ...dependencies]); + + return asyncState; +}; diff --git a/x-pack/plugins/profiling/public/hooks/use_default_date_range_redirect.ts b/x-pack/plugins/profiling/public/hooks/use_default_date_range_redirect.ts new file mode 100644 index 0000000000000..bb59b7d6b49f3 --- /dev/null +++ b/x-pack/plugins/profiling/public/hooks/use_default_date_range_redirect.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 qs from 'query-string'; +import { useHistory, useLocation } from 'react-router-dom'; +import { UI_SETTINGS } from '@kbn/data-plugin/public'; +import { useProfilingDependencies } from '../components/contexts/profiling_dependencies/use_profiling_dependencies'; + +export function useDateRangeRedirect() { + const history = useHistory(); + const location = useLocation(); + const query = qs.parse(location.search); + + const { + start: { core, data }, + } = useProfilingDependencies(); + + const timePickerTimeDefaults = core.uiSettings.get<{ from: string; to: string }>( + UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS + ); + + const timePickerSharedState = data.query.timefilter.timefilter.getTime(); + + const isDateRangeSet = 'rangeFrom' in query && 'rangeTo' in query; + + const redirect = () => { + const nextQuery = { + rangeFrom: timePickerSharedState.from ?? timePickerTimeDefaults.from, + rangeTo: timePickerSharedState.to ?? timePickerTimeDefaults.to, + ...query, + }; + + history.replace({ + ...location, + search: qs.stringify(nextQuery), + }); + }; + + return { + isDateRangeSet, + redirect, + }; +} diff --git a/x-pack/plugins/profiling/public/hooks/use_kibana_timezone_setting.ts b/x-pack/plugins/profiling/public/hooks/use_kibana_timezone_setting.ts new file mode 100644 index 0000000000000..411b6013154cd --- /dev/null +++ b/x-pack/plugins/profiling/public/hooks/use_kibana_timezone_setting.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 { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; +import { UI_SETTINGS } from '@kbn/data-plugin/public'; + +export function useKibanaTimeZoneSetting() { + const [kibanaTimeZone] = useUiSetting$(UI_SETTINGS.DATEFORMAT_TZ); + + if (!kibanaTimeZone || kibanaTimeZone === 'Browser') { + return 'local'; + } + + return kibanaTimeZone; +} diff --git a/x-pack/plugins/profiling/public/hooks/use_profiling_charts_theme.ts b/x-pack/plugins/profiling/public/hooks/use_profiling_charts_theme.ts new file mode 100644 index 0000000000000..cd72d1ae3b4b6 --- /dev/null +++ b/x-pack/plugins/profiling/public/hooks/use_profiling_charts_theme.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 { RecursivePartial, Theme } from '@elastic/charts'; +import { merge } from 'lodash'; +import { useProfilingDependencies } from '../components/contexts/profiling_dependencies/use_profiling_dependencies'; + +const profilingTheme: RecursivePartial = { + barSeriesStyle: { + rectBorder: { + strokeOpacity: 1, + strokeWidth: 1, + visible: true, + }, + rect: { + opacity: 0.6, + }, + }, + scales: { + barsPadding: 0, + histogramPadding: 0, + }, +}; + +export function useProfilingChartsTheme() { + const { + start: { charts }, + } = useProfilingDependencies(); + + const chartsBaseTheme = charts.theme.useChartsBaseTheme(); + const chartsTheme = charts.theme.useChartsTheme(); + + return { + chartsBaseTheme, + chartsTheme: merge({}, chartsTheme, profilingTheme), + }; +} diff --git a/x-pack/plugins/profiling/public/hooks/use_profiling_params.ts b/x-pack/plugins/profiling/public/hooks/use_profiling_params.ts new file mode 100644 index 0000000000000..7c4c1f8beaecd --- /dev/null +++ b/x-pack/plugins/profiling/public/hooks/use_profiling_params.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 { PathsOf, TypeOf, useParams } from '@kbn/typed-react-router-config'; +import { ValuesType } from 'utility-types'; +import { ProfilingRoutes } from '../routing'; + +export function useProfilingParams>( + path: T, + ...args: any[] +): TypeOf { + return useParams(path, ...args) as TypeOf; +} + +export function useAnyOfProfilingParams>>( + ...paths: TPaths +): TypeOf> { + return useParams(...paths)! as TypeOf>; +} diff --git a/x-pack/plugins/profiling/public/hooks/use_profiling_route_path.ts b/x-pack/plugins/profiling/public/hooks/use_profiling_route_path.ts new file mode 100644 index 0000000000000..91859984be453 --- /dev/null +++ b/x-pack/plugins/profiling/public/hooks/use_profiling_route_path.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PathsOf, useRoutePath } from '@kbn/typed-react-router-config'; +import { ProfilingRoutes } from '../routing'; + +export function useProfilingRoutePath(): PathsOf { + return useRoutePath() as PathsOf; +} diff --git a/x-pack/plugins/profiling/public/hooks/use_profiling_router.ts b/x-pack/plugins/profiling/public/hooks/use_profiling_router.ts new file mode 100644 index 0000000000000..0aa31af63dfa5 --- /dev/null +++ b/x-pack/plugins/profiling/public/hooks/use_profiling_router.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { PathsOf, TypeOf, TypeAsArgs } from '@kbn/typed-react-router-config'; +import { useHistory } from 'react-router-dom'; +import { useProfilingDependencies } from '../components/contexts/profiling_dependencies/use_profiling_dependencies'; +import { ProfilingRouter, profilingRouter, ProfilingRoutes } from '../routing'; + +export interface StatefulProfilingRouter extends ProfilingRouter { + push>( + path: T, + ...params: TypeAsArgs> + ): void; + replace>( + path: T, + ...params: TypeAsArgs> + ): void; +} + +export function useProfilingRouter(): StatefulProfilingRouter { + const history = useHistory(); + + const { + start: { core }, + } = useProfilingDependencies(); + + const link = (...args: any[]) => { + // @ts-expect-error + return profilingRouter.link(...args); + }; + + return { + ...profilingRouter, + push: (...args) => { + const next = link(...args); + + history.push(next); + }, + replace: (path, ...args) => { + const next = link(path, ...args); + history.replace(next); + }, + link: (path, ...args) => { + return core.http.basePath.prepend('/app/profiling' + link(path, ...args)); + }, + }; +} diff --git a/x-pack/plugins/profiling/public/hooks/use_time_range.ts b/x-pack/plugins/profiling/public/hooks/use_time_range.ts new file mode 100644 index 0000000000000..32e1b2c2cfb74 --- /dev/null +++ b/x-pack/plugins/profiling/public/hooks/use_time_range.ts @@ -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 { useMemo } from 'react'; +import { TimeRange } from '../../common/types'; +import { getNextTimeRange } from '../utils/get_next_time_range'; +import { useTimeRangeContext } from './use_time_range_context'; + +interface TimeRangeAPI { + timeRangeId: string; +} + +type PartialTimeRange = Pick, 'start' | 'end'>; + +export function useTimeRange(range: { + rangeFrom?: string; + rangeTo?: string; + optional: true; +}): TimeRangeAPI & PartialTimeRange; + +export function useTimeRange(range: { + rangeFrom: string; + rangeTo: string; +}): TimeRangeAPI & TimeRange; + +export function useTimeRange({ + rangeFrom, + rangeTo, + optional, +}: { + rangeFrom?: string; + rangeTo?: string; + optional?: boolean; +}): TimeRangeAPI & (TimeRange | PartialTimeRange) { + const timeRangeApi = useTimeRangeContext(); + + const { start, end } = useMemo(() => { + return getNextTimeRange({ + state: {}, + rangeFrom, + rangeTo, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [rangeFrom, rangeTo, timeRangeApi.timeRangeId]); + + if ((!start || !end) && !optional) { + throw new Error('start and/or end were unexpectedly not set'); + } + + return { + start, + end, + timeRangeId: timeRangeApi.timeRangeId, + }; +} diff --git a/x-pack/plugins/profiling/public/hooks/use_time_range_async.ts b/x-pack/plugins/profiling/public/hooks/use_time_range_async.ts new file mode 100644 index 0000000000000..2a4bffc639e57 --- /dev/null +++ b/x-pack/plugins/profiling/public/hooks/use_time_range_async.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 { UseAsync, useAsync } from './use_async'; +import { useTimeRangeContext } from './use_time_range_context'; + +export const useTimeRangeAsync: UseAsync = (fn, dependencies) => { + const { timeRangeId } = useTimeRangeContext(); + // eslint-disable-next-line react-hooks/exhaustive-deps + return useAsync(fn, dependencies.concat(timeRangeId)); +}; diff --git a/x-pack/plugins/profiling/public/hooks/use_time_range_context.ts b/x-pack/plugins/profiling/public/hooks/use_time_range_context.ts new file mode 100644 index 0000000000000..982f547eef21b --- /dev/null +++ b/x-pack/plugins/profiling/public/hooks/use_time_range_context.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 { useContext } from 'react'; +import { TimeRangeContext } from '../components/contexts/time_range_context'; + +export function useTimeRangeContext() { + const context = useContext(TimeRangeContext); + + if (!context) { + throw new Error('TimeRangeContext was not provided'); + } + + return context; +} diff --git a/x-pack/plugins/profiling/public/index.tsx b/x-pack/plugins/profiling/public/index.tsx new file mode 100644 index 0000000000000..974fcc27397b9 --- /dev/null +++ b/x-pack/plugins/profiling/public/index.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ProfilingPlugin } from './plugin'; + +export function plugin() { + return new ProfilingPlugin(); +} diff --git a/x-pack/plugins/profiling/public/plugin.tsx b/x-pack/plugins/profiling/public/plugin.tsx new file mode 100644 index 0000000000000..080941729b014 --- /dev/null +++ b/x-pack/plugins/profiling/public/plugin.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + AppMountParameters, + CoreSetup, + CoreStart, + DEFAULT_APP_CATEGORIES, + Plugin, +} from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; +import type { NavigationSection } from '@kbn/observability-plugin/public'; +import { Location } from 'history'; +import { BehaviorSubject, combineLatest, from, map } from 'rxjs'; +import { getServices } from './services'; +import type { ProfilingPluginPublicSetupDeps, ProfilingPluginPublicStartDeps } from './types'; + +export class ProfilingPlugin implements Plugin { + public setup(coreSetup: CoreSetup, pluginsSetup: ProfilingPluginPublicSetupDeps) { + // Register an application into the side navigation menu + + const links = [ + { + id: 'stacktraces', + title: i18n.translate('xpack.profiling.navigation.stacktracesLinkLabel', { + defaultMessage: 'Stacktraces', + }), + path: '/stacktraces', + }, + { + id: 'flamegraphs', + title: i18n.translate('xpack.profiling.navigation.flameGraphsLinkLabel', { + defaultMessage: 'Flamegraphs', + }), + path: '/flamegraphs', + }, + { + id: 'functions', + title: i18n.translate('xpack.profiling.navigation.functionsLinkLabel', { + defaultMessage: 'Functions', + }), + path: '/functions', + }, + ]; + + const kuerySubject = new BehaviorSubject(''); + + const section$ = combineLatest([from(coreSetup.getStartServices()), kuerySubject]).pipe( + map(([_, kuery]) => { + const sections: NavigationSection[] = [ + { + // TODO: add beta badge to section label, needs support in Observability plugin + label: i18n.translate('xpack.profiling.navigation.sectionLabel', { + defaultMessage: 'Universal Profiling', + }), + entries: links.map((link) => { + return { + app: 'profiling', + label: link.title, + path: `${link.path}?kuery=${kuery ?? ''}`, + matchPath: (path) => { + return path.startsWith(link.path); + }, + }; + }), + sortKey: 700, + }, + ]; + return sections; + }) + ); + + pluginsSetup.observability.navigation.registerSections(section$); + + coreSetup.application.register({ + id: 'profiling', + title: 'Universal Profiling', + euiIconType: 'logoObservability', + appRoute: '/app/profiling', + category: DEFAULT_APP_CATEGORIES.observability, + deepLinks: links, + async mount({ element, history, theme$ }: AppMountParameters) { + const [coreStart, pluginsStart] = (await coreSetup.getStartServices()) as [ + CoreStart, + ProfilingPluginPublicStartDeps, + unknown + ]; + + const profilingFetchServices = getServices(coreStart); + const { renderApp } = await import('./app'); + + function pushKueryToSubject(location: Location) { + const query = new URLSearchParams(location.search); + kuerySubject.next(query.get('kuery') ?? ''); + } + + pushKueryToSubject(history.location); + + history.listen(pushKueryToSubject); + + const unmount = renderApp( + { + profilingFetchServices, + coreStart, + coreSetup, + pluginsStart, + pluginsSetup, + history, + theme$, + }, + element + ); + + return () => { + unmount(); + kuerySubject.next(''); + }; + }, + }); + } + + public start(core: CoreStart) { + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/profiling/public/routing/index.tsx b/x-pack/plugins/profiling/public/routing/index.tsx new file mode 100644 index 0000000000000..d44f4a0655c00 --- /dev/null +++ b/x-pack/plugins/profiling/public/routing/index.tsx @@ -0,0 +1,205 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; +import { toNumberRt } from '@kbn/io-ts-utils'; +import { createRouter, Outlet } from '@kbn/typed-react-router-config'; +import * as t from 'io-ts'; +import React from 'react'; +import { TopNFunctionSortField, topNFunctionSortFieldRt } from '../../common/functions'; +import { StackTracesDisplayOption, TopNType } from '../../common/stack_traces'; +import { FlameGraphComparisonMode } from '../../common/flamegraph'; +import { FlameGraphsView } from '../components/flame_graphs_view'; +import { FunctionsView } from '../components/functions_view'; +import { RedirectTo } from '../components/redirect_to'; +import { RouteBreadcrumb } from '../components/route_breadcrumb'; +import { StackTracesView } from '../components/stack_traces_view'; + +const routes = { + '/': { + element: ( + + + + ), + children: { + '/': { + children: { + '/stacktraces/{topNType}': { + element: , + params: t.type({ + path: t.type({ + topNType: t.union([ + t.literal(TopNType.Containers), + t.literal(TopNType.Deployments), + t.literal(TopNType.Hosts), + t.literal(TopNType.Threads), + t.literal(TopNType.Traces), + ]), + }), + query: t.type({ + displayAs: t.union([ + t.literal(StackTracesDisplayOption.StackTraces), + t.literal(StackTracesDisplayOption.Percentage), + ]), + limit: toNumberRt, + }), + }), + defaults: { + query: { + displayAs: StackTracesDisplayOption.StackTraces, + limit: '10', + }, + }, + }, + '/stacktraces': { + element: , + }, + '/flamegraphs': { + element: ( + + + + + + ), + children: { + '/flamegraphs/flamegraph': { + element: ( + + + + ), + }, + '/flamegraphs/differential': { + element: ( + + + + ), + params: t.type({ + query: t.type({ + comparisonRangeFrom: t.string, + comparisonRangeTo: t.string, + comparisonKuery: t.string, + comparisonMode: t.union([ + t.literal(FlameGraphComparisonMode.Absolute), + t.literal(FlameGraphComparisonMode.Relative), + ]), + }), + }), + defaults: { + query: { + comparisonMode: FlameGraphComparisonMode.Absolute, + }, + }, + }, + }, + }, + '/functions': { + element: ( + + + + + + ), + params: t.type({ + query: t.type({ + sortField: topNFunctionSortFieldRt, + sortDirection: t.union([t.literal('asc'), t.literal('desc')]), + }), + }), + defaults: { + query: { + sortField: TopNFunctionSortField.Rank, + sortDirection: 'asc', + }, + }, + children: { + '/functions/topn': { + element: ( + + + + ), + }, + '/functions/differential': { + element: ( + + + + ), + params: t.type({ + query: t.type({ + comparisonRangeFrom: t.string, + comparisonRangeTo: t.string, + comparisonKuery: t.string, + }), + }), + }, + }, + }, + '/': { + element: , + }, + }, + element: , + params: t.type({ + query: t.type({ + rangeFrom: t.string, + rangeTo: t.string, + kuery: t.string, + }), + }), + defaults: { + query: { + kuery: '', + }, + }, + }, + }, + }, +}; + +export const profilingRouter = createRouter(routes); +export type ProfilingRoutes = typeof routes; +export type ProfilingRouter = typeof profilingRouter; diff --git a/x-pack/plugins/profiling/public/services.ts b/x-pack/plugins/profiling/public/services.ts new file mode 100644 index 0000000000000..07234ca124b36 --- /dev/null +++ b/x-pack/plugins/profiling/public/services.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { CoreStart, HttpFetchQuery } from '@kbn/core/public'; +import { getRoutePaths } from '../common'; +import { ElasticFlameGraph } from '../common/flamegraph'; +import { TopNFunctions } from '../common/functions'; +import { StackFrameMetadata } from '../common/profiling'; +import { TopNResponse } from '../common/topn'; + +export interface Services { + fetchTopN: (params: { + type: string; + timeFrom: number; + timeTo: number; + kuery: string; + }) => Promise; + fetchTopNFunctions: (params: { + timeFrom: number; + timeTo: number; + startIndex: number; + endIndex: number; + kuery: string; + }) => Promise; + fetchElasticFlamechart: (params: { + timeFrom: number; + timeTo: number; + kuery: string; + }) => Promise; + fetchFrameInformation: (params: { + frameID: string; + executableID: string; + }) => Promise; +} + +export function getServices(core: CoreStart): Services { + const paths = getRoutePaths(); + + return { + fetchTopN: async ({ type, timeFrom, timeTo, kuery }) => { + try { + const query: HttpFetchQuery = { + timeFrom, + timeTo, + kuery, + }; + return await core.http.get(`${paths.TopN}/${type}`, { query }); + } catch (e) { + return e; + } + }, + + fetchTopNFunctions: async ({ + timeFrom, + timeTo, + startIndex, + endIndex, + kuery, + }: { + timeFrom: number; + timeTo: number; + startIndex: number; + endIndex: number; + kuery: string; + }) => { + try { + const query: HttpFetchQuery = { + timeFrom, + timeTo, + startIndex, + endIndex, + kuery, + }; + return await core.http.get(paths.TopNFunctions, { query }); + } catch (e) { + return e; + } + }, + + fetchElasticFlamechart: async ({ + timeFrom, + timeTo, + kuery, + }: { + timeFrom: number; + timeTo: number; + kuery: string; + }) => { + try { + const query: HttpFetchQuery = { + timeFrom, + timeTo, + kuery, + }; + return await core.http.get(paths.Flamechart, { query }); + } catch (e) { + return e; + } + }, + fetchFrameInformation: async ({ + frameID, + executableID, + }: { + frameID: string; + executableID: string; + }) => { + try { + const query: HttpFetchQuery = { + frameID, + executableID, + }; + return await core.http.get(paths.FrameInformation, { query }); + } catch (e) { + return e; + } + }, + }; +} diff --git a/x-pack/plugins/profiling/public/types.ts b/x-pack/plugins/profiling/public/types.ts new file mode 100644 index 0000000000000..6c831e0b13509 --- /dev/null +++ b/x-pack/plugins/profiling/public/types.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { + DataViewsPublicPluginSetup, + DataViewsPublicPluginStart, +} from '@kbn/data-views-plugin/public'; +import type { + ObservabilityPublicSetup, + ObservabilityPublicStart, +} from '@kbn/observability-plugin/public'; +import { ChartsPluginSetup, ChartsPluginStart } from '@kbn/charts-plugin/public'; + +export interface ProfilingPluginPublicSetupDeps { + observability: ObservabilityPublicSetup; + dataViews: DataViewsPublicPluginSetup; + data: DataPublicPluginSetup; + charts: ChartsPluginSetup; +} + +export interface ProfilingPluginPublicStartDeps { + observability: ObservabilityPublicStart; + dataViews: DataViewsPublicPluginStart; + data: DataPublicPluginStart; + charts: ChartsPluginStart; +} diff --git a/x-pack/plugins/profiling/public/utils/formatters/as_cost.ts b/x-pack/plugins/profiling/public/utils/formatters/as_cost.ts new file mode 100644 index 0000000000000..148eba4785263 --- /dev/null +++ b/x-pack/plugins/profiling/public/utils/formatters/as_cost.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function asCost(value: number, precision: number = 2, unit: string = '$') { + return `${value.toPrecision(precision)}${unit}`; +} diff --git a/x-pack/plugins/profiling/public/utils/formatters/as_duration.ts b/x-pack/plugins/profiling/public/utils/formatters/as_duration.ts new file mode 100644 index 0000000000000..ba0839f06e779 --- /dev/null +++ b/x-pack/plugins/profiling/public/utils/formatters/as_duration.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; + +export function asDuration(valueInSeconds: number) { + return moment.duration(valueInSeconds * 1000).humanize(); +} diff --git a/x-pack/plugins/profiling/public/utils/formatters/as_percentage.ts b/x-pack/plugins/profiling/public/utils/formatters/as_percentage.ts new file mode 100644 index 0000000000000..f4c3a84b6275f --- /dev/null +++ b/x-pack/plugins/profiling/public/utils/formatters/as_percentage.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function asPercentage(value: number, precision: number = 0) { + return `${Number(value * 100).toFixed(precision)}%`; +} diff --git a/x-pack/plugins/profiling/public/utils/formatters/as_weight.ts b/x-pack/plugins/profiling/public/utils/formatters/as_weight.ts new file mode 100644 index 0000000000000..82a6cbd4f64b0 --- /dev/null +++ b/x-pack/plugins/profiling/public/utils/formatters/as_weight.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 { i18n } from '@kbn/i18n'; + +const ONE_POUND_TO_A_KILO = 0.45359237; + +export function asWeight(valueInPounds: number, precision: number = 2) { + const lbs = valueInPounds.toPrecision(precision); + const kgs = Number(valueInPounds * ONE_POUND_TO_A_KILO).toPrecision(precision); + + return i18n.translate('xpack.profiling.formatters.weight', { + defaultMessage: `{lbs} lbs / {kgs} kg`, + values: { + lbs, + kgs, + }, + }); +} diff --git a/x-pack/plugins/profiling/public/utils/get_flamegraph_model/get_interpolation_value.test.ts b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/get_interpolation_value.test.ts new file mode 100644 index 0000000000000..c9f123c020675 --- /dev/null +++ b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/get_interpolation_value.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { getInterpolationValue } from './get_interpolation_value'; + +describe('getInterpolationValue', () => { + it('returns 0 for no change', () => { + expect(getInterpolationValue(8, 8)).toBe(0); + }); + + it('returns -1 when the background is undefined', () => { + expect(getInterpolationValue(8, undefined)).toBe(1); + }); + + it('returns -1 when the background is 0', () => { + expect(getInterpolationValue(8, 0)).toBe(1); + }); + + it('returns 0 when both values are equal', () => { + expect(getInterpolationValue(1, 1)).toBe(0); + }); + + it('returns the correct positive change', () => { + expect(getInterpolationValue(8, 5)).toBe(0.375); + }); + + it('returns the correct negative change', () => { + expect(getInterpolationValue(5, 8)).toBe(-0.6); + }); + + it('returns the correct positive change with a denominator', () => { + expect(getInterpolationValue(10, 8, 50)).toBe(0.04); + }); + + it('returns the correct negative change with a denominator', () => { + expect(getInterpolationValue(8, 10, 50)).toBe(-0.04); + }); + + it('clamps changes', () => { + expect(getInterpolationValue(5, 12)).toBe(-1); + }); +}); diff --git a/x-pack/plugins/profiling/public/utils/get_flamegraph_model/get_interpolation_value.ts b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/get_interpolation_value.ts new file mode 100644 index 0000000000000..9abaa07dd0c62 --- /dev/null +++ b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/get_interpolation_value.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 { clamp } from 'lodash'; + +export function getInterpolationValue( + foreground: number, + background: number | undefined, + denominator: number = foreground +) { + if (background === undefined) { + return 1; + } + + return clamp((foreground - background) / denominator, -1, 1); +} diff --git a/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts new file mode 100644 index 0000000000000..1402897b966ac --- /dev/null +++ b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ColumnarViewModel } from '@elastic/charts'; +import d3 from 'd3'; +import { sum, uniqueId } from 'lodash'; +import { ElasticFlameGraph, FlameGraphComparisonMode, rgbToRGBA } from '../../../common/flamegraph'; +import { getInterpolationValue } from './get_interpolation_value'; + +const nullColumnarViewModel = { + label: [], + value: new Float64Array(), + color: new Float32Array(), + position0: new Float32Array(), + position1: new Float32Array(), + size0: new Float32Array(), + size1: new Float32Array(), +}; + +export function getFlamegraphModel({ + primaryFlamegraph, + comparisonFlamegraph, + colorSuccess, + colorDanger, + colorNeutral, + comparisonMode, +}: { + primaryFlamegraph?: ElasticFlameGraph; + comparisonFlamegraph?: ElasticFlameGraph; + colorSuccess: string; + colorDanger: string; + colorNeutral: string; + comparisonMode: FlameGraphComparisonMode; +}) { + const comparisonNodesById: Record< + string, + { Value: number; CountInclusive: number; CountExclusive: number } + > = {}; + + if (!primaryFlamegraph || !primaryFlamegraph.Label || primaryFlamegraph.Label.length === 0) { + return { key: uniqueId(), viewModel: nullColumnarViewModel, comparisonNodesById }; + } + + let colors: number[] | undefined = primaryFlamegraph.Color; + + if (comparisonFlamegraph) { + colors = []; + + comparisonFlamegraph.ID.forEach((nodeID, index) => { + comparisonNodesById[nodeID] = { + Value: comparisonFlamegraph.Value[index], + CountInclusive: comparisonFlamegraph.CountInclusive[index], + CountExclusive: comparisonFlamegraph.CountExclusive[index], + }; + }); + + const positiveChangeInterpolator = d3.interpolateRgb(colorNeutral, colorSuccess); + + const negativeChangeInterpolator = d3.interpolateRgb(colorNeutral, colorDanger); + + // per @thomasdullien: + // In "relative" mode: Take the percentage of CPU time consumed by block A and subtract + // the percentage of CPU time consumed by block B. If the number is positive, linearly + // interpolate a color between grey and green, with the delta relative to the size of + // block A as percentage. + + // Example 1: BlockA 8%, BlockB 5%, delta 3%. This represents a 3/8th reduction, 37.5% + // of the original time, so the color should be 37.5% "green". + + // Example 2: BlockA 5%, BlockB 8%, delta -3%. This represents a 3/5th worsening of BlockA, + // so the color should be 62.5% "red". In "absolute" mode: Take the number of samples in + // blockA, subtract the number of samples in blockB. Divide the result by the number of + // samples in the first graph. The result is the amount of saturation for the color. + // Example 3: BlockA 10k samples, BlockB 8k samples, total samples 50k. 10k-8k = 2k, 2k/50k + // = 4%, therefore 4% "green". + + const totalSamples = sum(primaryFlamegraph.CountExclusive); + const comparisonTotalSamples = sum(comparisonFlamegraph.CountExclusive); + + const weightComparisonSide = + comparisonMode === FlameGraphComparisonMode.Relative + ? 1 + : primaryFlamegraph.TotalSeconds / comparisonFlamegraph.TotalSeconds; + + primaryFlamegraph.ID.forEach((nodeID, index) => { + const samples = primaryFlamegraph.Value[index]; + const comparisonSamples = comparisonNodesById[nodeID]?.Value as number | undefined; + + const foreground = + comparisonMode === FlameGraphComparisonMode.Absolute ? samples : samples / totalSamples; + const background = + comparisonMode === FlameGraphComparisonMode.Absolute + ? comparisonSamples + : (comparisonSamples ?? 0) / comparisonTotalSamples; + + const denominator = + comparisonMode === FlameGraphComparisonMode.Absolute ? totalSamples : foreground; + + const interpolationValue = getInterpolationValue( + foreground, + background === undefined ? undefined : background * weightComparisonSide, + denominator + ); + + const nodeColor = + interpolationValue >= 0 + ? positiveChangeInterpolator(interpolationValue) + : negativeChangeInterpolator(Math.abs(interpolationValue)); + + colors!.push(...rgbToRGBA(Number(nodeColor.replace('#', '0x')))); + }); + } + + const value = new Float64Array(primaryFlamegraph.Value); + const position = new Float32Array(primaryFlamegraph.Position); + const size = new Float32Array(primaryFlamegraph.Size); + const color = new Float32Array(colors); + + return { + key: uniqueId(), + viewModel: { + label: primaryFlamegraph.Label, + value, + color, + position0: position, + position1: position, + size0: size, + size1: size, + } as ColumnarViewModel, + comparisonNodesById, + }; +} diff --git a/x-pack/plugins/profiling/public/utils/get_next_time_range/index.test.ts b/x-pack/plugins/profiling/public/utils/get_next_time_range/index.test.ts new file mode 100644 index 0000000000000..92d2c5457874d --- /dev/null +++ b/x-pack/plugins/profiling/public/utils/get_next_time_range/index.test.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 datemath from '@elastic/datemath'; +import moment from 'moment'; +import { getNextTimeRange } from '.'; + +describe('getNextTimeRange', () => { + beforeEach(() => { + jest.restoreAllMocks(); + }); + describe('getDateRange', () => { + describe('when rangeFrom and rangeTo are not changed', () => { + it('returns the previous state', () => { + expect( + getNextTimeRange({ + state: { + rangeFrom: 'now-1m', + rangeTo: 'now', + start: '1970-01-01T00:00:00.000Z', + end: '1971-01-01T00:00:00.000Z', + }, + rangeFrom: 'now-1m', + rangeTo: 'now', + }) + ).toEqual({ + start: '1970-01-01T00:00:00.000Z', + end: '1971-01-01T00:00:00.000Z', + }); + }); + }); + + describe('when rangeFrom or rangeTo are falsy', () => { + it('returns the previous state', () => { + // Disable console warning about not receiving a valid date for rangeFrom + jest.spyOn(console, 'warn').mockImplementationOnce(() => {}); + + expect( + getNextTimeRange({ + state: { + start: '1972-01-01T00:00:00.000Z', + end: '1973-01-01T00:00:00.000Z', + }, + rangeFrom: '', + rangeTo: 'now', + }) + ).toEqual({ + start: '1972-01-01T00:00:00.000Z', + end: '1973-01-01T00:00:00.000Z', + }); + }); + }); + + describe('when the start or end are invalid', () => { + it('returns the previous state', () => { + const endDate = moment('2021-06-04T18:03:24.211Z'); + jest.spyOn(datemath, 'parse').mockReturnValueOnce(undefined).mockReturnValueOnce(endDate); + + expect( + getNextTimeRange({ + state: { + start: '1972-01-01T00:00:00.000Z', + end: '1973-01-01T00:00:00.000Z', + }, + rangeFrom: 'nope', + rangeTo: 'now', + }) + ).toEqual({ + start: '1972-01-01T00:00:00.000Z', + end: '1973-01-01T00:00:00.000Z', + }); + }); + }); + + describe('when rangeFrom or rangeTo have changed', () => { + it('returns new state', () => { + jest.spyOn(Date, 'now').mockReturnValue(moment(0).unix()); + + expect( + getNextTimeRange({ + state: { + rangeFrom: 'now-1m', + rangeTo: 'now', + start: '1972-01-01T00:00:00.000Z', + end: '1973-01-01T00:00:00.000Z', + }, + rangeFrom: 'now-2m', + rangeTo: 'now', + }) + ).toEqual({ + start: '1969-12-31T23:58:00.000Z', + end: '1970-01-01T00:00:00.000Z', + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/profiling/public/utils/get_next_time_range/index.ts b/x-pack/plugins/profiling/public/utils/get_next_time_range/index.ts new file mode 100644 index 0000000000000..27414d2d9427a --- /dev/null +++ b/x-pack/plugins/profiling/public/utils/get_next_time_range/index.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 datemath from '@elastic/datemath'; + +function getParsedDate(rawDate?: string, options = {}) { + if (rawDate) { + const parsed = datemath.parse(rawDate, options); + if (parsed && parsed.isValid()) { + return parsed.toDate(); + } + } +} + +export function getNextTimeRange({ + state = {}, + rangeFrom, + rangeTo, +}: { + state?: { rangeFrom?: string; rangeTo?: string; start?: string; end?: string }; + rangeFrom?: string; + rangeTo?: string; +}) { + // If the previous state had the same range, just return that instead of calculating a new range. + if (state.rangeFrom === rangeFrom && state.rangeTo === rangeTo) { + return { + start: state.start, + end: state.end, + }; + } + const start = getParsedDate(rangeFrom); + const end = getParsedDate(rangeTo, { roundUp: true }); + + // `getParsedDate` will return undefined for invalid or empty dates. We return + // the previous state if either date is undefined. + if (!start || !end) { + return { + start: state.start, + end: state.end, + }; + } + + return { + start: start.toISOString(), + end: end.toISOString(), + }; +} diff --git a/x-pack/plugins/profiling/scripts/export_from_testing.sh b/x-pack/plugins/profiling/scripts/export_from_testing.sh new file mode 100755 index 0000000000000..84c76415e3351 --- /dev/null +++ b/x-pack/plugins/profiling/scripts/export_from_testing.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash + +# Exit on use of undefined variables and on command failures. +set -eu + +die() { + if [[ "$@" ]]; then + echo -e "$@\n" >&2 + fi + exit 1 +} + +usage() { + die "Usage: $0 [options]\n" \ + " --dateFrom Start of data, date format (e.g. \"2022-02-28T00:00:00Z\") or a unix timestamp\n" \ + " --dateTo End of data\n" \ + "\n" \ + "The data will be exported into the current working directory.\n" \ + "\n" \ + "You need to put the credentials into the ELASTIC_TESTING_CREDENTIALS env var (...=\"user:pw\")" +} + +get_timestamp() { + local date_input=$1 + + if [[ "${date_input}" =~ ^[0-9]+$ ]]; then + echo "${date_input}" + else + echo $(date +%s --date="${date_input}") + fi +} + +export_events() { + local index=$1 + local from=$2 + local to=$3 + + docker run --rm -ti --net=host -v "$PWD:/data" -w /data -e "NODE_OPTIONS=--max_old_space_size=8192" \ + elasticdump/elasticsearch-dump:latest \ + --input="https://""${ELASTIC_TESTING_CREDENTIALS}""@profiling-8-5-rc.es.us-west2.gcp.elastic-cloud.com/${index}" \ + --output="${index}-data_${from}_${to}.json.gz" \ + --type=data --fsCompress --noRefresh --limit=100000 --support-big-int \ + --searchBody=' +{ + "query": { + "bool": { + "filter": [ + { + "term": { + "service.name": "922" + } + }, + { + "range": { + "@timestamp": { + "gte": "'"$from"'", + "lt": "'"$to"'", + "format": "epoch_second" + } + } + } + ] + } + } +}' +} + +export_index() { + local index=$1 + + docker run --rm -ti --net=host -v "$PWD:/data" -w /data -e "NODE_OPTIONS=--max_old_space_size=8192" \ + elasticdump/elasticsearch-dump:latest \ + --input="https://""${ELASTIC_TESTING_CREDENTIALS}""@profiling-8-5-rc.es.us-west2.gcp.elastic-cloud.com/${index}" \ + --output="${index}-data.json.gz" \ + --type=data --fsCompress --noRefresh --limit=100000 --support-big-int +} + +if [[ "$ELASTIC_TESTING_CREDENTIALS" == "" ]]; then + usage +fi + +date_from=0 +date_to=$(date +%s --date="now") + +while [[ $# -gt 0 ]]; do + case $1 in + --dateFrom) + date_from=$(get_timestamp "$2") + shift 2 + ;; + --dateTo) + date_to=$(get_timestamp "$2") + shift 2 + ;; + *) + usage + ;; + esac +done + +[[ "$date_to" -le "$date_from" ]] && die "Invalid time range" + +# Pull latest docker image +docker pull elasticdump/elasticsearch-dump:latest + +# Export full events table +export_events "profiling-events-all" "$date_from" "$date_to" + +# Export down-sampled tables +for ((i = 1; i <= 11; i++)); do + export_events "profiling-events-5pow$(printf '%02d' $i)" "$date_from" "$date_to" +done + +# We need all stacktraces, stackframes and executables as 'LastSeen' may be outside [date_from, date_to]. +export_index "profiling-stacktraces" +export_index "profiling-stackframes" +export_index "profiling-executables" + +echo -e ' + Import the data with ... + or upload to S3 with + for i in profiling-*; do aws s3 cp $i s3://oblt-profiling-es-snapshot/; done +' diff --git a/x-pack/plugins/profiling/scripts/import_from_testing.sh b/x-pack/plugins/profiling/scripts/import_from_testing.sh new file mode 100755 index 0000000000000..f5848632f1996 --- /dev/null +++ b/x-pack/plugins/profiling/scripts/import_from_testing.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +# Exit on use of undefined variables and on command failures. +set -eu + +die() { + if [[ "$@" ]]; then + echo -e "$@\n" >&2 + fi + exit 1 +} + +usage() { + die "Usage: $0 \n" \ + "\n" \ + "The data files are imported into the local ES instance (localhost:9200).\n" \ + "The naming convention is that the first part (before -data...) is the index name.\n" + "\n" \ + "You need to put the credentials into the ELASTIC_LOCAL_CREDENTIALS env var (...=\"user:pw\")" +} + +import() { + local index=$1 + local path=$2 + local dir=${path%$file} + + if [[ "$dir" == "" ]]; then + dir="." + fi + + pushd "$dir" + docker run --rm -ti --net=host -v "$PWD:/data" elasticdump/elasticsearch-dump:latest \ + --input="/data/$file" \ + --output="http://admin:changeme@localhost:9200/$index" \ + --type=data --fsCompress --noRefresh --support-big-int --limit=10000 + popd +} + +while [[ $# -gt 0 ]]; do + path=$1 + file=${1##*/} + index=${file%-data*} + + import "$index" "$path" + shift +done diff --git a/x-pack/plugins/profiling/server/feature.ts b/x-pack/plugins/profiling/server/feature.ts new file mode 100644 index 0000000000000..e6d9c3098dc03 --- /dev/null +++ b/x-pack/plugins/profiling/server/feature.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 { i18n } from '@kbn/i18n'; +import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; + +export const PROFILING_SERVER_FEATURE_ID = 'profiling'; + +export const PROFILING_FEATURE = { + id: PROFILING_SERVER_FEATURE_ID, + name: i18n.translate('xpack.profiling.featureRegistry.profilingFeatureName', { + defaultMessage: 'Universal Profiling', + }), + order: 1200, + category: DEFAULT_APP_CATEGORIES.observability, + app: ['kibana'], + catalogue: [], + // see x-pack/plugins/features/common/feature_kibana_privileges.ts + privileges: { + all: { + app: ['kibana'], + catalogue: [], + savedObject: { + all: [], + read: [], + }, + ui: ['show'], + }, + read: { + app: ['kibana'], + catalogue: [], + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + }, +}; diff --git a/x-pack/plugins/profiling/server/index.ts b/x-pack/plugins/profiling/server/index.ts new file mode 100644 index 0000000000000..26858b770b736 --- /dev/null +++ b/x-pack/plugins/profiling/server/index.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import type { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server'; +import { ProfilingPlugin } from './plugin'; + +const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: false }), +}); + +type ProfilingConfig = TypeOf; + +// plugin config +export const config: PluginConfigDescriptor = { + schema: configSchema, +}; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. + +export function plugin(initializerContext: PluginInitializerContext) { + return new ProfilingPlugin(initializerContext); +} + +export type { ProfilingPluginSetup, ProfilingPluginStart } from './types'; diff --git a/x-pack/plugins/profiling/server/plugin.ts b/x-pack/plugins/profiling/server/plugin.ts new file mode 100644 index 0000000000000..40071b54c0c4e --- /dev/null +++ b/x-pack/plugins/profiling/server/plugin.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 { CoreSetup, CoreStart, Logger, Plugin, PluginInitializerContext } from '@kbn/core/server'; + +import { PROFILING_FEATURE } from './feature'; +import { registerRoutes } from './routes'; +import { + ProfilingPluginSetup, + ProfilingPluginSetupDeps, + ProfilingPluginStart, + ProfilingPluginStartDeps, + ProfilingRequestHandlerContext, +} from './types'; + +export class ProfilingPlugin + implements + Plugin< + ProfilingPluginSetup, + ProfilingPluginStart, + ProfilingPluginSetupDeps, + ProfilingPluginStartDeps + > +{ + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup, deps: ProfilingPluginSetupDeps) { + this.logger.debug('profiling: Setup'); + const router = core.http.createRouter(); + + deps.features.registerKibanaFeature(PROFILING_FEATURE); + + core.getStartServices().then(([_, depsStart]) => { + registerRoutes({ + router, + logger: this.logger!, + dependencies: { + start: depsStart, + setup: deps, + }, + }); + }); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('profiling: Started'); + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/profiling/server/routes/compat.ts b/x-pack/plugins/profiling/server/routes/compat.ts new file mode 100644 index 0000000000000..6969e84bff6e7 --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/compat.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. + */ + +// Code that works around incompatibilities between different +// versions of Kibana / ES. +// Currently, we work with 8.1 and 8.3 and thus this code only needs +// to address the incompatibilities between those two versions. + +import type { ElasticsearchClient } from '@kbn/core/server'; +import { ProfilingRequestHandlerContext } from '../types'; + +export async function getClient( + context: ProfilingRequestHandlerContext +): Promise { + return (await context.core).elasticsearch.client.asCurrentUser; +} diff --git a/x-pack/plugins/profiling/server/routes/downsampling.ts b/x-pack/plugins/profiling/server/routes/downsampling.ts new file mode 100644 index 0000000000000..e1280dd0903ca --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/downsampling.ts @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 } from '@kbn/core/server'; +import seedrandom from 'seedrandom'; +import { StackTraceID } from '../../common/profiling'; +import { ProfilingESClient } from '../utils/create_profiling_es_client'; +import { ProjectTimeQuery } from './query'; + +export interface DownsampledEventsIndex { + name: string; + sampleRate: number; +} + +function getFullDownsampledIndex(index: string, pow: number, factor: number): string { + const downsampledIndexPrefix = index.replaceAll('-all', '') + '-' + factor + 'pow'; + return downsampledIndexPrefix + pow.toString().padStart(2, '0'); +} + +// Return the index that has between targetSampleSize..targetSampleSize*samplingFactor entries. +// The starting point is the number of entries from the profiling-events-5pow index. +// +// More details on how the down-sampling works can be found at the write path +// https://github.com/elastic/prodfiler/blob/bdcc2711c6cd7e89d63b58a17329fb9fdbabe008/pf-elastic-collector/elastic.go +export function getSampledTraceEventsIndex( + index: string, + targetSampleSize: number, + sampleCountFromInitialExp: number, + initialExp: number +): DownsampledEventsIndex { + const maxExp = 11; + const samplingFactor = 5; + const fullEventsIndex: DownsampledEventsIndex = { name: index, sampleRate: 1 }; + + if (sampleCountFromInitialExp === 0) { + // Take the shortcut to the full events index. + return fullEventsIndex; + } + + let pow = Math.floor( + initialExp - + Math.log((targetSampleSize * samplingFactor) / sampleCountFromInitialExp) / Math.log(5) + + 1 + ); + + if (pow < 1) { + return fullEventsIndex; + } + + if (pow > maxExp) { + pow = maxExp; + } + + return { + name: getFullDownsampledIndex(index, pow, samplingFactor), + sampleRate: 1 / samplingFactor ** pow, + }; +} + +export async function findDownsampledIndex({ + logger, + client, + index, + filter, + sampleSize, +}: { + logger: Logger; + client: ProfilingESClient; + index: string; + filter: ProjectTimeQuery; + sampleSize: number; +}): Promise { + // Start with counting the results in the index down-sampled by 5^6. + // That is in the middle of our down-sampled indexes. + const initialExp = 6; + let sampleCountFromInitialExp = 0; + try { + const resp = await client.search('find_downsampled_index', { + index: getFullDownsampledIndex(index, initialExp, 5), + body: { + query: filter, + size: 0, + track_total_hits: true, + }, + }); + sampleCountFromInitialExp = resp.hits.total.value; + } catch (e) { + logger.info(e.message); + } + + logger.info('sampleCountFromPow6 ' + sampleCountFromInitialExp); + return getSampledTraceEventsIndex(index, sampleSize, sampleCountFromInitialExp, initialExp); +} + +export function downsampleEventsRandomly( + stackTraceEvents: Map, + p: number, + seed: string +): number { + let totalCount = 0; + + // Make the RNG predictable to get reproducible results. + const random = seedrandom(seed); + + for (const [id, count] of stackTraceEvents) { + let newCount = 0; + for (let i = 0; i < count; i++) { + if (random() < p) { + newCount++; + } + } + if (newCount) { + stackTraceEvents.set(id, newCount); + totalCount += newCount; + } else { + stackTraceEvents.delete(id); + } + } + + return totalCount; +} diff --git a/x-pack/plugins/profiling/server/routes/flamechart.test.ts b/x-pack/plugins/profiling/server/routes/flamechart.test.ts new file mode 100644 index 0000000000000..e4e527082aade --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/flamechart.test.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 { DownsampledEventsIndex, getSampledTraceEventsIndex } from './downsampling'; + +describe('Using down-sampled indexes', () => { + test('getSampledTraceEventsIndex', () => { + const targetSampleSize = 20000; + const initialExp = 6; + const tests: Array<{ + sampleCountFromPow6: number; + expected: DownsampledEventsIndex; + }> = [ + { + // stay with the input downsampled index + sampleCountFromPow6: targetSampleSize, + expected: { name: 'profiling-events-5pow06', sampleRate: 1 / 5 ** 6 }, + }, + { + // stay with the input downsampled index + sampleCountFromPow6: targetSampleSize * 5 - 1, + expected: { name: 'profiling-events-5pow06', sampleRate: 1 / 5 ** 6 }, + }, + { + // go down one downsampling step + sampleCountFromPow6: targetSampleSize * 5, + expected: { name: 'profiling-events-5pow07', sampleRate: 1 / 5 ** 7 }, + }, + { + // go up one downsampling step + sampleCountFromPow6: targetSampleSize - 1, + expected: { name: 'profiling-events-5pow05', sampleRate: 1 / 5 ** 5 }, + }, + { + // go to the full events index + sampleCountFromPow6: 0, + expected: { name: 'profiling-events-all', sampleRate: 1 }, + }, + { + // go to the most downsampled index + sampleCountFromPow6: targetSampleSize * 5 ** 8, + expected: { name: 'profiling-events-5pow11', sampleRate: 1 / 5 ** 11 }, + }, + ]; + + for (const t of tests) { + expect( + getSampledTraceEventsIndex( + 'profiling-events-all', + targetSampleSize, + t.sampleCountFromPow6, + initialExp + ) + ).toEqual(t.expected); + } + }); +}); diff --git a/x-pack/plugins/profiling/server/routes/flamechart.ts b/x-pack/plugins/profiling/server/routes/flamechart.ts new file mode 100644 index 0000000000000..4c27d87ea39eb --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/flamechart.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 { schema } from '@kbn/config-schema'; +import { RouteRegisterParameters } from '.'; +import { getRoutePaths } from '../../common'; +import { FlameGraph } from '../../common/flamegraph'; +import { createProfilingEsClient } from '../utils/create_profiling_es_client'; +import { withProfilingSpan } from '../utils/with_profiling_span'; +import { getClient } from './compat'; +import { getExecutablesAndStackTraces } from './get_executables_and_stacktraces'; +import { createCommonFilter } from './query'; + +export function registerFlameChartSearchRoute({ router, logger }: RouteRegisterParameters) { + const paths = getRoutePaths(); + router.get( + { + path: paths.Flamechart, + validate: { + query: schema.object({ + timeFrom: schema.number(), + timeTo: schema.number(), + kuery: schema.string(), + }), + }, + }, + async (context, request, response) => { + const { timeFrom, timeTo, kuery } = request.query; + const targetSampleSize = 20000; // minimum number of samples to get statistically sound results + + try { + const esClient = await getClient(context); + const filter = createCommonFilter({ + timeFrom, + timeTo, + kuery, + }); + + const { stackTraces, executables, stackFrames, eventsIndex, totalCount, stackTraceEvents } = + await getExecutablesAndStackTraces({ + logger, + client: createProfilingEsClient({ request, esClient }), + filter, + sampleSize: targetSampleSize, + }); + + const flamegraph = await withProfilingSpan('collect_flamegraph', async () => { + return new FlameGraph({ + sampleRate: eventsIndex.sampleRate, + totalCount, + events: stackTraceEvents, + stackTraces, + stackFrames, + executables, + totalSeconds: timeTo - timeFrom, + }).toElastic(); + }); + + logger.info('returning payload response to client'); + + return response.ok({ + body: flamegraph, + }); + } catch (e) { + logger.error(e); + return response.customError({ + statusCode: e.statusCode ?? 500, + body: { + message: e.message, + }, + }); + } + } + ); +} diff --git a/x-pack/plugins/profiling/server/routes/frames.ts b/x-pack/plugins/profiling/server/routes/frames.ts new file mode 100644 index 0000000000000..4a0ce745c7246 --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/frames.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { Logger } from '@kbn/logging'; +import { RouteRegisterParameters } from '.'; +import { getRoutePaths } from '../../common'; +import { + createStackFrameMetadata, + Executable, + StackFrame, + StackFrameMetadata, +} from '../../common/profiling'; +import { createProfilingEsClient, ProfilingESClient } from '../utils/create_profiling_es_client'; +import { mgetStackFrames, mgetExecutables } from './stacktrace'; + +async function getFrameInformation({ + frameID, + executableID, + logger, + client, +}: { + frameID: string; + executableID: string; + logger: Logger; + client: ProfilingESClient; +}): Promise { + const [stackFrames, executables] = await Promise.all([ + mgetStackFrames({ + logger, + client, + stackFrameIDs: new Set([frameID]), + }), + mgetExecutables({ + logger, + client, + executableIDs: new Set([executableID]), + }), + ]); + + const frame = Array.from(stackFrames.values())[0] as StackFrame | undefined; + const executable = Array.from(executables.values())[0] as Executable | undefined; + + if (frame) { + return createStackFrameMetadata({ + FrameID: frameID, + FileID: executableID, + SourceFilename: frame.FileName, + FunctionName: frame.FunctionName, + ExeFileName: executable?.FileName, + }); + } +} + +export function registerFrameInformationRoute(params: RouteRegisterParameters) { + const { logger, router } = params; + + const routePaths = getRoutePaths(); + + router.get( + { + path: routePaths.FrameInformation, + validate: { + query: schema.object({ + frameID: schema.string(), + executableID: schema.string(), + }), + }, + }, + async (context, request, response) => { + const { frameID, executableID } = request.query; + + const client = createProfilingEsClient({ + request, + esClient: (await context.core).elasticsearch.client.asCurrentUser, + }); + + try { + const frame = await getFrameInformation({ + frameID, + executableID, + logger, + client, + }); + + return response.ok({ body: frame }); + } catch (error: any) { + logger.error(error); + return response.custom({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An internal server error occured', + }, + }); + } + } + ); +} diff --git a/x-pack/plugins/profiling/server/routes/functions.ts b/x-pack/plugins/profiling/server/routes/functions.ts new file mode 100644 index 0000000000000..dffc56e8aee74 --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/functions.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 { schema, TypeOf } from '@kbn/config-schema'; +import { RouteRegisterParameters } from '.'; +import { getRoutePaths } from '../../common'; +import { createTopNFunctions } from '../../common/functions'; +import { createProfilingEsClient } from '../utils/create_profiling_es_client'; +import { withProfilingSpan } from '../utils/with_profiling_span'; +import { getClient } from './compat'; +import { getExecutablesAndStackTraces } from './get_executables_and_stacktraces'; +import { createCommonFilter } from './query'; + +const querySchema = schema.object({ + timeFrom: schema.number(), + timeTo: schema.number(), + startIndex: schema.number(), + endIndex: schema.number(), + kuery: schema.string(), +}); + +type QuerySchemaType = TypeOf; + +export function registerTopNFunctionsSearchRoute({ router, logger }: RouteRegisterParameters) { + const paths = getRoutePaths(); + router.get( + { + path: paths.TopNFunctions, + validate: { + query: querySchema, + }, + }, + async (context, request, response) => { + try { + const { timeFrom, timeTo, startIndex, endIndex, kuery }: QuerySchemaType = request.query; + + const targetSampleSize = 20000; // minimum number of samples to get statistically sound results + const esClient = await getClient(context); + const filter = createCommonFilter({ + timeFrom, + timeTo, + kuery, + }); + + const { stackFrames, stackTraceEvents, stackTraces, executables } = + await getExecutablesAndStackTraces({ + client: createProfilingEsClient({ request, esClient }), + filter, + logger, + sampleSize: targetSampleSize, + }); + + const topNFunctions = await withProfilingSpan('collect_topn_functions', async () => { + return createTopNFunctions( + stackTraceEvents, + stackTraces, + stackFrames, + executables, + startIndex, + endIndex + ); + }); + + logger.info('returning payload response to client'); + + return response.ok({ + body: topNFunctions, + }); + } catch (e) { + logger.error(e); + return response.customError({ + statusCode: e.statusCode ?? 500, + body: { + message: e.message, + }, + }); + } + } + ); +} diff --git a/x-pack/plugins/profiling/server/routes/get_executables_and_stacktraces.ts b/x-pack/plugins/profiling/server/routes/get_executables_and_stacktraces.ts new file mode 100644 index 0000000000000..9e5773ad9aa91 --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/get_executables_and_stacktraces.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Logger } from '@kbn/logging'; +import { INDEX_EVENTS } from '../../common'; +import { ProfilingESClient } from '../utils/create_profiling_es_client'; +import { withProfilingSpan } from '../utils/with_profiling_span'; +import { downsampleEventsRandomly, findDownsampledIndex } from './downsampling'; +import { logExecutionLatency } from './logger'; +import { ProjectTimeQuery } from './query'; +import { + mgetExecutables, + mgetStackFrames, + mgetStackTraces, + searchEventsGroupByStackTrace, +} from './stacktrace'; + +export async function getExecutablesAndStackTraces({ + logger, + client, + filter, + sampleSize, +}: { + logger: Logger; + client: ProfilingESClient; + filter: ProjectTimeQuery; + sampleSize: number; +}) { + return withProfilingSpan('get_executables_and_stack_traces', async () => { + const eventsIndex = await findDownsampledIndex({ + logger, + client, + index: INDEX_EVENTS, + filter, + sampleSize, + }); + + const { totalCount, stackTraceEvents } = await searchEventsGroupByStackTrace({ + logger, + client, + index: eventsIndex, + filter, + }); + + // Manual downsampling if totalCount exceeds sampleSize by 10%. + let p = 1.0; + if (totalCount > sampleSize * 1.1) { + p = sampleSize / totalCount; + logger.info('downsampling events with p=' + p); + await logExecutionLatency(logger, 'downsampling events', async () => { + const downsampledTotalCount = downsampleEventsRandomly( + stackTraceEvents, + p, + filter.toString() + ); + logger.info('downsampled total count: ' + downsampledTotalCount); + }); + logger.info('unique downsampled stacktraces: ' + stackTraceEvents.size); + } + + // Adjust the sample counts from down-sampled to fully sampled. + // Be aware that downsampling drops entries from stackTraceEvents, so that + // the sum of the upscaled count values is less that totalCount. + for (const [id, count] of stackTraceEvents) { + stackTraceEvents.set(id, Math.floor(count / (eventsIndex.sampleRate * p))); + } + + const { stackTraces, stackFrameDocIDs, executableDocIDs } = await mgetStackTraces({ + logger, + client, + events: stackTraceEvents, + }); + + return withProfilingSpan('get_stackframes_and_executables', () => + Promise.all([ + mgetStackFrames({ logger, client, stackFrameIDs: stackFrameDocIDs }), + mgetExecutables({ logger, client, executableIDs: executableDocIDs }), + ]) + ).then(([stackFrames, executables]) => { + return { + stackTraces, + executables, + stackFrames, + stackTraceEvents, + totalCount, + eventsIndex, + }; + }); + }); +} diff --git a/x-pack/plugins/profiling/server/routes/index.ts b/x-pack/plugins/profiling/server/routes/index.ts new file mode 100644 index 0000000000000..6e44bf6909585 --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/index.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IRouter, Logger } from '@kbn/core/server'; +import { + ProfilingPluginSetupDeps, + ProfilingPluginStartDeps, + ProfilingRequestHandlerContext, +} from '../types'; + +import { registerFlameChartSearchRoute } from './flamechart'; +import { registerFrameInformationRoute } from './frames'; +import { registerTopNFunctionsSearchRoute } from './functions'; + +import { + registerTraceEventsTopNContainersSearchRoute, + registerTraceEventsTopNDeploymentsSearchRoute, + registerTraceEventsTopNHostsSearchRoute, + registerTraceEventsTopNStackTracesSearchRoute, + registerTraceEventsTopNThreadsSearchRoute, +} from './topn'; + +export interface RouteRegisterParameters { + router: IRouter; + logger: Logger; + dependencies: { + start: ProfilingPluginStartDeps; + setup: ProfilingPluginSetupDeps; + }; +} + +export function registerRoutes(params: RouteRegisterParameters) { + registerFlameChartSearchRoute(params); + registerTopNFunctionsSearchRoute(params); + registerTraceEventsTopNContainersSearchRoute(params); + registerTraceEventsTopNDeploymentsSearchRoute(params); + registerTraceEventsTopNHostsSearchRoute(params); + registerTraceEventsTopNStackTracesSearchRoute(params); + registerTraceEventsTopNThreadsSearchRoute(params); + registerFrameInformationRoute(params); +} diff --git a/x-pack/plugins/profiling/server/routes/logger.ts b/x-pack/plugins/profiling/server/routes/logger.ts new file mode 100644 index 0000000000000..8d4c82962faec --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/logger.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 type { Logger } from '@kbn/core/server'; + +export async function logExecutionLatency( + logger: Logger, + activity: string, + func: () => Promise +): Promise { + const start = new Date().getTime(); + return await func().then((res) => { + logger.info(activity + ' took ' + (new Date().getTime() - start) + 'ms'); + return res; + }); +} diff --git a/x-pack/plugins/profiling/server/routes/query.ts b/x-pack/plugins/profiling/server/routes/query.ts new file mode 100644 index 0000000000000..f8a776ee68ce7 --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/query.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 { QueryDslBoolQuery } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { kqlQuery } from '@kbn/observability-plugin/server'; +import { ProfilingESField } from '../../common/elasticsearch'; + +export interface ProjectTimeQuery { + bool: QueryDslBoolQuery; +} + +export function createCommonFilter({ + kuery, + timeFrom, + timeTo, +}: { + kuery: string; + timeFrom: number; + timeTo: number; +}): ProjectTimeQuery { + return { + bool: { + filter: [ + ...kqlQuery(kuery), + { + range: { + [ProfilingESField.Timestamp]: { + gte: String(timeFrom), + lt: String(timeTo), + format: 'epoch_second', + boost: 1.0, + }, + }, + }, + ], + }, + }; +} diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.test.ts b/x-pack/plugins/profiling/server/routes/stacktrace.test.ts new file mode 100644 index 0000000000000..54a9f7e15fc67 --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/stacktrace.test.ts @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { createStackFrameID, StackTrace } from '../../common/profiling'; +import { + decodeStackTrace, + EncodedStackTrace, + runLengthDecode, + runLengthEncode, +} from './stacktrace'; + +enum fileID { + A = 'aQpJmTLWydNvOapSFZOwKg', + B = 'hz_u-HGyrN6qeIk6UIJeCA', + C = 'AJ8qrcXSoJbl_haPhlc4og', + D = 'lHZiv7a58px6Gumcpo-6yA', + E = 'fkbxUTZgljnk71ZMnqJnyA', + F = 'gnEsgxvvEODj6iFYMQWYlA', +} + +enum addressOrLine { + A = 515512, + B = 26278522, + C = 6712518, + D = 105806025, + E = 111, + F = 106182663, + G = 100965370, +} + +const frameID: Record = { + A: createStackFrameID(fileID.A, addressOrLine.A), + B: createStackFrameID(fileID.B, addressOrLine.B), + C: createStackFrameID(fileID.C, addressOrLine.C), + D: createStackFrameID(fileID.D, addressOrLine.D), + E: createStackFrameID(fileID.E, addressOrLine.E), + F: createStackFrameID(fileID.F, addressOrLine.F), + G: createStackFrameID(fileID.F, addressOrLine.G), +}; + +const frameTypeA = [0, 0, 0]; +const frameTypeB = [8, 8, 8, 8]; + +describe('Stack trace operations', () => { + test('decodeStackTrace', () => { + const tests: Array<{ + original: EncodedStackTrace; + expected: StackTrace; + }> = [ + { + original: { + Stacktrace: { + frame: { + ids: frameID.A + frameID.B + frameID.C, + types: runLengthEncode(frameTypeA).toString('base64url'), + }, + }, + } as EncodedStackTrace, + expected: { + FrameIDs: [frameID.A, frameID.B, frameID.C], + FileIDs: [fileID.A, fileID.B, fileID.C], + AddressOrLines: [addressOrLine.A, addressOrLine.B, addressOrLine.C], + Types: frameTypeA, + } as StackTrace, + }, + { + original: { + Stacktrace: { + frame: { + ids: frameID.D + frameID.E + frameID.F + frameID.G, + types: runLengthEncode(frameTypeB).toString('base64url'), + }, + }, + } as EncodedStackTrace, + expected: { + FrameIDs: [frameID.D, frameID.E, frameID.F, frameID.G], + FileIDs: [fileID.D, fileID.E, fileID.F, fileID.F], + AddressOrLines: [addressOrLine.D, addressOrLine.E, addressOrLine.F, addressOrLine.G], + Types: frameTypeB, + } as StackTrace, + }, + ]; + + for (const t of tests) { + expect(decodeStackTrace(t.original)).toEqual(t.expected); + } + }); + + test('run length is fully reversible', () => { + const tests: number[][] = [[], [0], [0, 1, 2, 3], [0, 1, 1, 2, 2, 2, 3, 3, 3, 3]]; + + for (const t of tests) { + expect(runLengthDecode(runLengthEncode(t))).toEqual(t); + } + }); + + test('runLengthDecodeReverse with optional parameter', () => { + const tests: Array<{ + bytes: Buffer; + expected: number[]; + }> = [ + { + bytes: Buffer.from([0x5, 0x0, 0x2, 0x2]), + expected: [0, 0, 0, 0, 0, 2, 2], + }, + { + bytes: Buffer.from([0x1, 0x8]), + expected: [8], + }, + ]; + + for (const t of tests) { + expect(runLengthDecode(t.bytes, t.expected.length)).toEqual(t.expected); + } + }); + + test('runLengthDecodeReverse without optional parameter', () => { + const tests: Array<{ + bytes: Buffer; + expected: number[]; + }> = [ + { + bytes: Buffer.from([0x5, 0x0, 0x2, 0x2]), + expected: [0, 0, 0, 0, 0, 2, 2], + }, + { + bytes: Buffer.from([0x1, 0x8]), + expected: [8], + }, + ]; + + for (const t of tests) { + expect(runLengthDecode(t.bytes)).toEqual(t.expected); + } + }); +}); diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.ts b/x-pack/plugins/profiling/server/routes/stacktrace.ts new file mode 100644 index 0000000000000..ecf51313695fa --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/stacktrace.ts @@ -0,0 +1,417 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 } from '@kbn/core/server'; +import { chunk } from 'lodash'; +import LRUCache from 'lru-cache'; +import { INDEX_EXECUTABLES, INDEX_FRAMES, INDEX_TRACES } from '../../common'; +import { + DedotObject, + PickFlattened, + ProfilingESField, + ProfilingExecutable, + ProfilingStackFrame, + ProfilingStackTrace, +} from '../../common/elasticsearch'; +import { + Executable, + FileID, + StackFrame, + StackFrameID, + StackTrace, + StackTraceID, +} from '../../common/profiling'; +import { ProfilingESClient } from '../utils/create_profiling_es_client'; +import { withProfilingSpan } from '../utils/with_profiling_span'; +import { DownsampledEventsIndex } from './downsampling'; +import { logExecutionLatency } from './logger'; +import { ProjectTimeQuery } from './query'; + +const traceLRU = new LRUCache({ max: 20000 }); + +const BASE64_FRAME_ID_LENGTH = 32; + +export type EncodedStackTrace = DedotObject<{ + // This field is a base64-encoded byte string. The string represents a + // serialized list of frame IDs in which the order of frames are + // reversed to allow for prefix compression (leaf frame last). Each + // frame ID is composed of two concatenated values: a 16-byte file ID + // and an 8-byte address or line number (depending on the context of + // the downstream reader). + // + // Frame ID #1 Frame ID #2 + // +----------------+--------+----------------+--------+---- + // | File ID | Addr | File ID | Addr | + // +----------------+--------+----------------+--------+---- + [ProfilingESField.StacktraceFrameIDs]: string; + + // This field is a run-length encoding of a list of uint8s. The order is + // reversed from the original input. + [ProfilingESField.StacktraceFrameTypes]: string; +}>; + +// runLengthEncode run-length encodes the input array. +// +// The input is a list of uint8s. The output is a binary stream of +// 2-byte pairs (first byte is the length and the second byte is the +// binary representation of the object) in reverse order. +// +// E.g. uint8 array [0, 0, 0, 0, 0, 2, 2, 2] is converted into the byte +// array [5, 0, 3, 2]. +export function runLengthEncode(input: number[]): Buffer { + const output: number[] = []; + + if (input.length === 0) { + return Buffer.from(output); + } + + let count = 0; + let current = input[0]; + + for (let i = 1; i < input.length; i++) { + const next = input[i]; + + if (next === current && count < 255) { + count++; + continue; + } + + output.push(count + 1, current); + + count = 0; + current = next; + } + + output.push(count + 1, current); + + return Buffer.from(output); +} + +// runLengthDecode decodes a run-length encoding for the input array. +// +// The input is a binary stream of 2-byte pairs (first byte is the length and the +// second byte is the binary representation of the object). The output is a list of +// uint8s. +// +// E.g. byte array [5, 0, 3, 2] is converted into an uint8 array like +// [0, 0, 0, 0, 0, 2, 2, 2]. +export function runLengthDecode(input: Buffer, outputSize?: number): number[] { + let size; + + if (typeof outputSize === 'undefined') { + size = 0; + for (let i = 0; i < input.length; i += 2) { + size += input[i]; + } + } else { + size = outputSize; + } + + const output: number[] = new Array(size); + + let idx = 0; + for (let i = 0; i < input.length; i += 2) { + for (let j = 0; j < input[i]; j++) { + output[idx] = input[i + 1]; + idx++; + } + } + + return output; +} + +// decodeStackTrace unpacks an encoded stack trace from Elasticsearch +export function decodeStackTrace(input: EncodedStackTrace): StackTrace { + const inputFrameIDs = input.Stacktrace.frame.ids; + const inputFrameTypes = input.Stacktrace.frame.types; + const countsFrameIDs = inputFrameIDs.length / BASE64_FRAME_ID_LENGTH; + + const fileIDs: string[] = new Array(countsFrameIDs); + const frameIDs: string[] = new Array(countsFrameIDs); + const addressOrLines: number[] = new Array(countsFrameIDs); + + // Step 1: Convert the base64-encoded frameID list into two separate + // lists (frame IDs and file IDs), both of which are also base64-encoded. + // + // To get the frame ID, we grab the next 32 bytes. + // + // To get the file ID, we grab the first 22 bytes of the frame ID. + // However, since the file ID is base64-encoded using 21.33 bytes + // (16 * 4 / 3), then the 22 bytes have an extra 4 bits from the + // address (see diagram in definition of EncodedStackTrace). + for (let i = 0; i < countsFrameIDs; i++) { + const pos = i * BASE64_FRAME_ID_LENGTH; + const frameID = inputFrameIDs.slice(pos, pos + BASE64_FRAME_ID_LENGTH); + const buf = Buffer.from(frameID, 'base64url'); + + fileIDs[i] = buf.toString('base64url', 0, 16); + addressOrLines[i] = Number(buf.readBigUInt64BE(16)); + frameIDs[i] = frameID; + } + + // Step 2: Convert the run-length byte encoding into a list of uint8s. + const types = Buffer.from(inputFrameTypes, 'base64url'); + const typeIDs = runLengthDecode(types, countsFrameIDs); + + return { + AddressOrLines: addressOrLines, + FileIDs: fileIDs, + FrameIDs: frameIDs, + Types: typeIDs, + } as StackTrace; +} + +export async function searchEventsGroupByStackTrace({ + logger, + client, + index, + filter, +}: { + logger: Logger; + client: ProfilingESClient; + index: DownsampledEventsIndex; + filter: ProjectTimeQuery; +}) { + const resEvents = await client.search('get_events_group_by_stack_trace', { + index: index.name, + track_total_hits: false, + query: filter, + aggs: { + group_by: { + terms: { + // 'size' should be max 100k, but might be slightly more. Better be on the safe side. + size: 150000, + field: ProfilingESField.StacktraceID, + // 'execution_hint: map' skips the slow building of ordinals that we don't need. + // Especially with high cardinality fields, this makes aggregations really slow. + // E.g. it reduces the latency from 70s to 0.7s on our 8.1. MVP cluster (as of 28.04.2022). + execution_hint: 'map', + }, + aggs: { + count: { + sum: { + field: ProfilingESField.StacktraceCount, + }, + }, + }, + }, + total_count: { + sum: { + field: ProfilingESField.StacktraceCount, + }, + }, + }, + pre_filter_shard_size: 1, + filter_path: + 'aggregations.group_by.buckets.key,aggregations.group_by.buckets.count,aggregations.total_count,_shards.failures', + }); + + const totalCount = resEvents.aggregations?.total_count.value ?? 0; + const stackTraceEvents = new Map(); + + resEvents.aggregations?.group_by?.buckets.forEach((item) => { + const traceid: StackTraceID = String(item.key); + stackTraceEvents.set(traceid, item.count.value ?? 0); + }); + + logger.info('events total count: ' + totalCount); + logger.info('unique stacktraces: ' + stackTraceEvents.size); + + return { totalCount, stackTraceEvents }; +} + +export async function mgetStackTraces({ + logger, + client, + events, + concurrency = 1, +}: { + logger: Logger; + client: ProfilingESClient; + events: Map; + concurrency?: number; +}) { + const stackTraceIDs = [...events.keys()]; + const chunkSize = Math.floor(events.size / concurrency); + let chunks = chunk(stackTraceIDs, chunkSize); + + if (chunks.length !== concurrency) { + // The last array element contains the remainder, just drop it as irrelevant. + chunks = chunks.slice(0, concurrency); + } + + const stackResponses = await withProfilingSpan('mget_stacktraces', () => + Promise.all( + chunks.map((ids) => { + return client.mget< + PickFlattened< + ProfilingStackTrace, + ProfilingESField.StacktraceFrameIDs | ProfilingESField.StacktraceFrameTypes + > + >('mget_stacktraces_chunk', { + index: INDEX_TRACES, + ids, + realtime: true, + _source_includes: [ + ProfilingESField.StacktraceFrameIDs, + ProfilingESField.StacktraceFrameTypes, + ], + }); + }) + ) + ); + + let totalFrames = 0; + const stackTraces = new Map(); + const stackFrameDocIDs = new Set(); + const executableDocIDs = new Set(); + + await logExecutionLatency(logger, 'processing data', async () => { + // flatMap() is significantly slower than an explicit for loop + for (const res of stackResponses) { + for (const trace of res.docs) { + if ('error' in trace) { + continue; + } + // Sometimes we don't find the trace. + // This is due to ES delays writing (data is not immediately seen after write). + // Also, ES doesn't know about transactions. + if (trace.found) { + const traceid = trace._id as StackTraceID; + let stackTrace = traceLRU.get(traceid) as StackTrace; + if (!stackTrace) { + stackTrace = decodeStackTrace(trace._source as EncodedStackTrace); + traceLRU.set(traceid, stackTrace); + } + + totalFrames += stackTrace.FrameIDs.length; + stackTraces.set(traceid, stackTrace); + for (const frameID of stackTrace.FrameIDs) { + stackFrameDocIDs.add(frameID); + } + for (const fileID of stackTrace.FileIDs) { + executableDocIDs.add(fileID); + } + } + } + } + }); + + if (stackTraces.size !== 0) { + logger.info('Average size of stacktrace: ' + totalFrames / stackTraces.size); + } + + if (stackTraces.size < events.size) { + logger.info( + 'failed to find ' + (events.size - stackTraces.size) + ' stacktraces (todo: find out why)' + ); + } + + return { stackTraces, stackFrameDocIDs, executableDocIDs }; +} + +export async function mgetStackFrames({ + logger, + client, + stackFrameIDs, +}: { + logger: Logger; + client: ProfilingESClient; + stackFrameIDs: Set; +}): Promise> { + const stackFrames = new Map(); + + if (stackFrameIDs.size === 0) { + return stackFrames; + } + + const resStackFrames = await client.mget('mget_stackframes', { + index: INDEX_FRAMES, + ids: [...stackFrameIDs], + realtime: true, + }); + + // Create a lookup map StackFrameID -> StackFrame. + let framesFound = 0; + await logExecutionLatency(logger, 'processing data', async () => { + const docs = resStackFrames.docs; + for (const frame of docs) { + if ('error' in frame) { + continue; + } + if (frame.found) { + stackFrames.set(frame._id, { + FileName: frame._source!.Stackframe.file?.name, + FunctionName: frame._source!.Stackframe.function?.name, + FunctionOffset: frame._source!.Stackframe.function?.offset, + LineNumber: frame._source!.Stackframe.line?.number, + SourceType: frame._source!.Stackframe.source?.type, + }); + framesFound++; + } else { + stackFrames.set(frame._id, { + FileName: '', + FunctionName: '', + FunctionOffset: 0, + LineNumber: 0, + SourceType: 0, + }); + } + } + }); + + logger.info('found ' + framesFound + ' / ' + stackFrameIDs.size + ' frames'); + + return stackFrames; +} + +export async function mgetExecutables({ + logger, + client, + executableIDs, +}: { + logger: Logger; + client: ProfilingESClient; + executableIDs: Set; +}): Promise> { + const executables = new Map(); + + if (executableIDs.size === 0) { + return executables; + } + + const resExecutables = await client.mget('mget_executables', { + index: INDEX_EXECUTABLES, + ids: [...executableIDs], + _source_includes: [ProfilingESField.ExecutableFileName], + }); + + // Create a lookup map StackFrameID -> StackFrame. + let exeFound = 0; + await logExecutionLatency(logger, 'processing data', async () => { + const docs = resExecutables.docs; + for (const exe of docs) { + if ('error' in exe) { + continue; + } + if (exe.found) { + executables.set(exe._id, { + FileName: exe._source!.Executable.file.name, + }); + exeFound++; + } else { + executables.set(exe._id, { + FileName: '', + }); + } + } + }); + + logger.info('found ' + exeFound + ' / ' + executableIDs.size + ' executables'); + + return executables; +} diff --git a/x-pack/plugins/profiling/server/routes/topn.test.ts b/x-pack/plugins/profiling/server/routes/topn.test.ts new file mode 100644 index 0000000000000..2185db03d4ca3 --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/topn.test.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/types'; +import { coreMock } from '@kbn/core/server/mocks'; +import { loggerMock } from '@kbn/logging-mocks'; +import { ProfilingESField } from '../../common/elasticsearch'; +import { ProfilingESClient } from '../utils/create_profiling_es_client'; +import { topNElasticSearchQuery } from './topn'; + +const anyQuery = 'any::query'; +const smallestInterval = '1s'; +const testAgg = { aggs: { test: {} } }; + +jest.mock('./query', () => ({ + createCommonFilter: ({}: {}) => { + return anyQuery; + }, + findFixedIntervalForBucketsPerTimeRange: (from: number, to: number, buckets: number): string => { + return smallestInterval; + }, + aggregateByFieldAndTimestamp: ( + searchField: string, + interval: string + ): AggregationsAggregationContainer => { + return testAgg; + }, +})); + +describe('TopN data from Elasticsearch', () => { + const context = coreMock.createRequestHandlerContext(); + const client: ProfilingESClient = { + search: jest.fn( + (operationName, request) => + context.elasticsearch.client.asCurrentUser.search(request) as Promise + ), + mget: jest.fn( + (operationName, request) => + context.elasticsearch.client.asCurrentUser.search(request) as Promise + ), + }; + const logger = loggerMock.create(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('when fetching Stack Traces', () => { + it('should search first then skip mget', async () => { + await topNElasticSearchQuery({ + client, + logger, + timeFrom: 456, + timeTo: 789, + searchField: ProfilingESField.StacktraceID, + highCardinality: false, + kuery: '', + }); + + // Calls to mget are skipped since data doesn't exist + expect(client.search).toHaveBeenCalledTimes(2); + expect(client.mget).toHaveBeenCalledTimes(0); + }); + }); +}); diff --git a/x-pack/plugins/profiling/server/routes/topn.ts b/x-pack/plugins/profiling/server/routes/topn.ts new file mode 100644 index 0000000000000..296fcc1a1463a --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/topn.ts @@ -0,0 +1,276 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { IRouter, Logger } from '@kbn/core/server'; +import { RouteRegisterParameters } from '.'; +import { fromMapToRecord, getRoutePaths, INDEX_EVENTS } from '../../common'; +import { ProfilingESField } from '../../common/elasticsearch'; +import { computeBucketWidthFromTimeRangeAndBucketCount } from '../../common/histogram'; +import { groupStackFrameMetadataByStackTrace, StackTraceID } from '../../common/profiling'; +import { getFieldNameForTopNType, TopNType } from '../../common/stack_traces'; +import { createTopNSamples, getTopNAggregationRequest, TopNResponse } from '../../common/topn'; +import { ProfilingRequestHandlerContext } from '../types'; +import { createProfilingEsClient, ProfilingESClient } from '../utils/create_profiling_es_client'; +import { withProfilingSpan } from '../utils/with_profiling_span'; +import { getClient } from './compat'; +import { findDownsampledIndex } from './downsampling'; +import { createCommonFilter } from './query'; +import { mgetExecutables, mgetStackFrames, mgetStackTraces } from './stacktrace'; + +export async function topNElasticSearchQuery({ + client, + logger, + timeFrom, + timeTo, + searchField, + highCardinality, + kuery, +}: { + client: ProfilingESClient; + logger: Logger; + timeFrom: number; + timeTo: number; + searchField: string; + highCardinality: boolean; + kuery: string; +}): Promise { + const filter = createCommonFilter({ timeFrom, timeTo, kuery }); + const targetSampleSize = 20000; // minimum number of samples to get statistically sound results + + const bucketWidth = computeBucketWidthFromTimeRangeAndBucketCount(timeFrom, timeTo, 50); + + const eventsIndex = await findDownsampledIndex({ + logger, + client, + index: INDEX_EVENTS, + filter, + sampleSize: targetSampleSize, + }); + + const resEvents = await client.search('get_topn_histogram', { + index: eventsIndex.name, + size: 0, + query: filter, + aggs: getTopNAggregationRequest({ + searchField, + highCardinality, + fixedInterval: `${bucketWidth}s`, + }), + // Adrien and Dario found out this is a work-around for some bug in 8.1. + // It reduces the query time by avoiding unneeded searches. + pre_filter_shard_size: 1, + }); + + const { aggregations } = resEvents; + + if (!aggregations) { + return { + TotalCount: 0, + TopN: [], + Metadata: {}, + Labels: {}, + }; + } + + // Creating top N samples requires the time range and bucket width to + // be in milliseconds, not seconds + const topN = createTopNSamples(aggregations, timeFrom * 1000, timeTo * 1000, bucketWidth * 1000); + + for (let i = 0; i < topN.length; i++) { + topN[i].Count = (topN[i].Count ?? 0) / eventsIndex.sampleRate; + } + + const groupByBuckets = aggregations.group_by.buckets ?? []; + + const labels: Record = {}; + + for (const bucket of groupByBuckets) { + if (bucket.sample?.top[0]) { + labels[String(bucket.key)] = String( + bucket.sample.top[0].metrics[ProfilingESField.HostName] || + bucket.sample.top[0].metrics[ProfilingESField.HostIP] || + '' + ); + } + } + + let totalSampledStackTraces = aggregations.total_count.value ?? 0; + logger.info('total sampled stacktraces: ' + totalSampledStackTraces); + totalSampledStackTraces = Math.floor(totalSampledStackTraces / eventsIndex.sampleRate); + + if (searchField !== ProfilingESField.StacktraceID) { + return { + TotalCount: totalSampledStackTraces, + TopN: topN, + Metadata: {}, + Labels: labels, + }; + } + + const stackTraceEvents = new Map(); + let totalAggregatedStackTraces = 0; + + for (let i = 0; i < groupByBuckets.length; i++) { + const stackTraceID = String(groupByBuckets[i].key); + const count = Math.floor((groupByBuckets[i].count.value ?? 0) / eventsIndex.sampleRate); + totalAggregatedStackTraces += count; + stackTraceEvents.set(stackTraceID, count); + } + + logger.info('total aggregated stacktraces: ' + totalAggregatedStackTraces); + logger.info('unique aggregated stacktraces: ' + stackTraceEvents.size); + + const { stackTraces, stackFrameDocIDs, executableDocIDs } = await mgetStackTraces({ + logger, + client, + events: stackTraceEvents, + }); + + const [stackFrames, executables] = await withProfilingSpan( + 'get_stackframes_and_executables', + () => { + return Promise.all([ + mgetStackFrames({ logger, client, stackFrameIDs: stackFrameDocIDs }), + mgetExecutables({ logger, client, executableIDs: executableDocIDs }), + ]); + } + ); + + const metadata = await withProfilingSpan('collect_stackframe_metadata', async () => { + return fromMapToRecord( + groupStackFrameMetadataByStackTrace(stackTraces, stackFrames, executables) + ); + }); + + logger.info('returning payload response to client'); + + return { + TotalCount: totalSampledStackTraces, + TopN: topN, + Metadata: metadata, + Labels: labels, + }; +} + +export function queryTopNCommon( + router: IRouter, + logger: Logger, + pathName: string, + searchField: string, + highCardinality: boolean +) { + router.get( + { + path: pathName, + validate: { + query: schema.object({ + timeFrom: schema.number(), + timeTo: schema.number(), + kuery: schema.string(), + }), + }, + }, + async (context, request, response) => { + const { timeFrom, timeTo, kuery } = request.query; + const client = await getClient(context); + + try { + return response.ok({ + body: await topNElasticSearchQuery({ + client: createProfilingEsClient({ request, esClient: client }), + logger, + timeFrom, + timeTo, + searchField, + highCardinality, + kuery, + }), + }); + } catch (e) { + logger.error(e); + + return response.customError({ + statusCode: e.statusCode ?? 500, + body: { + message: 'Profiling TopN request failed: ' + e.message + '; full error ' + e.toString(), + }, + }); + } + } + ); +} + +export function registerTraceEventsTopNContainersSearchRoute({ + router, + logger, +}: RouteRegisterParameters) { + const paths = getRoutePaths(); + return queryTopNCommon( + router, + logger, + paths.TopNContainers, + getFieldNameForTopNType(TopNType.Containers), + false + ); +} + +export function registerTraceEventsTopNDeploymentsSearchRoute({ + router, + logger, +}: RouteRegisterParameters) { + const paths = getRoutePaths(); + return queryTopNCommon( + router, + logger, + paths.TopNDeployments, + getFieldNameForTopNType(TopNType.Deployments), + false + ); +} + +export function registerTraceEventsTopNHostsSearchRoute({ + router, + logger, +}: RouteRegisterParameters) { + const paths = getRoutePaths(); + return queryTopNCommon( + router, + logger, + paths.TopNHosts, + getFieldNameForTopNType(TopNType.Hosts), + false + ); +} + +export function registerTraceEventsTopNStackTracesSearchRoute({ + router, + logger, +}: RouteRegisterParameters) { + const paths = getRoutePaths(); + return queryTopNCommon( + router, + logger, + paths.TopNTraces, + getFieldNameForTopNType(TopNType.Traces), + false + ); +} + +export function registerTraceEventsTopNThreadsSearchRoute({ + router, + logger, +}: RouteRegisterParameters) { + const paths = getRoutePaths(); + return queryTopNCommon( + router, + logger, + paths.TopNThreads, + getFieldNameForTopNType(TopNType.Threads), + true + ); +} diff --git a/x-pack/plugins/profiling/server/types.ts b/x-pack/plugins/profiling/server/types.ts new file mode 100644 index 0000000000000..8432085ef1022 --- /dev/null +++ b/x-pack/plugins/profiling/server/types.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RequestHandlerContext } from '@kbn/core/server'; +import { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; +import { ObservabilityPluginSetup } from '@kbn/observability-plugin/server'; + +export interface ProfilingPluginSetupDeps { + observability: ObservabilityPluginSetup; + features: FeaturesPluginSetup; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ProfilingPluginStartDeps {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ProfilingPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ProfilingPluginStart {} + +export type ProfilingRequestHandlerContext = RequestHandlerContext; diff --git a/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts b/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts new file mode 100644 index 0000000000000..a7b985a567a4e --- /dev/null +++ b/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { ElasticsearchClient } from '@kbn/core/server'; +import { ESSearchRequest, InferSearchResponseOf } from '@kbn/core/types/elasticsearch'; +import type { KibanaRequest } from '@kbn/core/server'; +import { unwrapEsResponse } from '@kbn/observability-plugin/server'; +import { MgetRequest, MgetResponse } from '@elastic/elasticsearch/lib/api/types'; +import { ProfilingESEvent } from '../../common/elasticsearch'; +import { withProfilingSpan } from './with_profiling_span'; + +export function cancelEsRequestOnAbort>( + promise: T, + request: KibanaRequest, + controller: AbortController +): T { + const subscription = request.events.aborted$.subscribe(() => { + controller.abort(); + }); + + return promise.finally(() => subscription.unsubscribe()) as T; +} + +export interface ProfilingESClient { + search( + operationName: string, + searchRequest: TSearchRequest + ): Promise>; + mget( + operationName: string, + mgetRequest: MgetRequest + ): Promise>; +} + +export function createProfilingEsClient({ + request, + esClient, +}: { + request: KibanaRequest; + esClient: ElasticsearchClient; +}): ProfilingESClient { + return { + search( + operationName: string, + searchRequest: TSearchRequest + ): Promise> { + const controller = new AbortController(); + + const promise = withProfilingSpan(operationName, () => { + return cancelEsRequestOnAbort( + esClient.search(searchRequest, { + signal: controller.signal, + meta: true, + }) as unknown as Promise<{ + body: InferSearchResponseOf; + }>, + request, + controller + ); + }); + + return unwrapEsResponse(promise); + }, + mget( + operationName: string, + mgetRequest: MgetRequest + ): Promise> { + const controller = new AbortController(); + + const promise = withProfilingSpan(operationName, () => { + return cancelEsRequestOnAbort( + esClient.mget(mgetRequest, { + signal: controller.signal, + meta: true, + }), + request, + controller + ); + }); + + return unwrapEsResponse(promise); + }, + }; +} diff --git a/x-pack/plugins/profiling/server/utils/with_profiling_span.ts b/x-pack/plugins/profiling/server/utils/with_profiling_span.ts new file mode 100644 index 0000000000000..6d366799780e7 --- /dev/null +++ b/x-pack/plugins/profiling/server/utils/with_profiling_span.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { withSpan, SpanOptions, parseSpanOptions } from '@kbn/apm-utils'; + +export function withProfilingSpan( + optionsOrName: SpanOptions | string, + cb: () => Promise +): Promise { + const options = parseSpanOptions(optionsOrName); + + const optionsWithDefaults = { + ...(options.intercept ? {} : { type: 'plugin:profiling' }), + ...options, + labels: { + plugin: 'profiling', + ...options.labels, + }, + }; + + return withSpan(optionsWithDefaults, cb); +} diff --git a/x-pack/plugins/profiling/tsconfig.json b/x-pack/plugins/profiling/tsconfig.json new file mode 100644 index 0000000000000..5b8daabf46cbe --- /dev/null +++ b/x-pack/plugins/profiling/tsconfig.json @@ -0,0 +1,45 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + }, + "include": [ + // add all the folders containing files to be compiled + "index.ts", + "common/**/*.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts" + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/data/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../../../src/plugins/navigation/tsconfig.json" }, + { "path": "../../../src/plugins/share/tsconfig.json" }, + { "path": "../observability/tsconfig.json" }, + // add references to other TypeScript projects the plugin depends on + + // requiredPlugins from ./kibana.json + // { "path": "../licensing/tsconfig.json" }, + // { "path": "../../../src/plugins/data/tsconfig.json" }, + // { "path": "../encrypted_saved_objects/tsconfig.json" }, + + // optionalPlugins from ./kibana.json + // { "path": "../security/tsconfig.json" }, + // { "path": "../features/tsconfig.json" }, + // { "path": "../cloud/tsconfig.json" }, + // { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, + // { "path": "../../../src/plugins/home/tsconfig.json" }, + + // requiredBundles from ./kibana.json + // { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + // { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" }, + // { "path": "../infra/tsconfig.json" }, + // { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 622c74efd8281..7a19089905608 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -317,6 +317,7 @@ export const TIMELINE_PREPACKAGED_URL = `${TIMELINE_URL}/_prepackaged` as const; export const NOTE_URL = '/api/note' as const; export const PINNED_EVENT_URL = '/api/pinned_event' as const; export const SOURCERER_API_URL = '/internal/security_solution/sourcerer' as const; +export const RISK_SCORE_INDEX_STATUS_API_URL = '/internal/risk_score/index_status' as const; /** * Default signals index key for kibana.dev.yml diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts index 57603fb3576f4..1d7943bdefa1b 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts @@ -47,14 +47,14 @@ describe('actions schemas', () => { }).not.toThrow(); }); - it('should limit multiple agent IDs in an array to 50', () => { + it('should not limit multiple agent IDs', () => { expect(() => { EndpointActionListRequestSchema.query.validate({ - agentIds: Array(51) + agentIds: Array(255) .fill(1) .map(() => uuid.v4()), }); - }).toThrow(); + }).not.toThrow(); }); it('should work with all required query params', () => { 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 d147d78a421f1..08adac7c9ede0 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts @@ -87,7 +87,7 @@ export const EndpointActionListRequestSchema = { query: schema.object({ agentIds: schema.maybe( schema.oneOf([ - schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1, maxSize: 50 }), + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), schema.string({ minLength: 1 }), ]) ), diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index cf0222e6f1faa..bc908da4b43e4 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -17,13 +17,10 @@ export const allowedExperimentalValues = Object.freeze({ excludePoliciesInFilterEnabled: false, kubernetesEnabled: true, disableIsolationUIPendingStatuses: false, - riskyHostsEnabled: false, - riskyUsersEnabled: false, pendingActionResponsesWithAck: true, policyListEnabled: true, policyResponseInFleetEnabled: true, threatIntelligenceEnabled: false, - entityAnalyticsDashboardEnabled: false, /** * This is used for enabling the end-to-end tests for the security_solution telemetry. diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts new file mode 100644 index 0000000000000..9b1d5c71a6fa9 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts @@ -0,0 +1,237 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { getNewRule } from '../../objects/rule'; + +import { + DEFINE_CONTINUE_BUTTON, + CUSTOM_QUERY_BAR, + LOAD_QUERY_DYNAMICALLY_CHECKBOX, + QUERY_BAR, +} from '../../screens/create_new_rule'; +import { TOASTER } from '../../screens/alerts_detection_rules'; +import { + RULE_NAME_HEADER, + SAVED_QUERY_NAME_DETAILS, + SAVED_QUERY_DETAILS, + SAVED_QUERY_FILTERS_DETAILS, + DEFINE_RULE_PANEL_PROGRESS, + CUSTOM_QUERY_DETAILS, +} from '../../screens/rule_details'; + +import { goToRuleDetails, editFirstRule } from '../../tasks/alerts_detection_rules'; +import { createTimeline } from '../../tasks/api_calls/timelines'; +import { createSavedQuery } from '../../tasks/api_calls/saved_queries'; +import { cleanKibana, deleteAlertsAndRules, deleteSavedQueries } from '../../tasks/common'; +import { + createAndEnableRule, + fillAboutRuleAndContinue, + fillScheduleRuleAndContinue, + selectAndLoadSavedQuery, + getCustomQueryInput, + checkLoadQueryDynamically, + uncheckLoadQueryDynamically, +} from '../../tasks/create_new_rule'; +import { saveEditedRule } from '../../tasks/edit_rule'; +import { login, visit } from '../../tasks/login'; +import { getDetails } from '../../tasks/rule_details'; +import { createSavedQueryRule, createCustomRule } from '../../tasks/api_calls/rules'; + +import { RULE_CREATION, SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation'; + +const savedQueryName = 'custom saved query'; +const savedQueryQuery = 'process.name: test'; +const savedQueryFilterKey = 'testAgent.value'; + +describe('Custom saved_query rules', () => { + before(() => { + cleanKibana(); + login(); + }); + describe('Custom saved_query detection rule creation', () => { + beforeEach(() => { + deleteAlertsAndRules(); + deleteSavedQueries(); + createTimeline(getNewRule().timeline).then((response) => { + cy.wrap({ + ...getNewRule(), + timeline: { + ...getNewRule().timeline, + id: response.body.data.persistTimeline.timeline.savedObjectId, + }, + }).as('rule'); + }); + }); + + it('Creates saved query rule', function () { + createSavedQuery(savedQueryName, savedQueryQuery, savedQueryFilterKey); + visit(RULE_CREATION); + + selectAndLoadSavedQuery(savedQueryName, savedQueryQuery); + + // edit loaded saved query + getCustomQueryInput() + .type(' AND random query') + .should('have.value', [savedQueryQuery, ' AND random query'].join('')); + + // when clicking load query dynamically checkbox, saved query should be shown in query input and input should be disabled + checkLoadQueryDynamically(); + getCustomQueryInput().should('have.value', savedQueryQuery).should('be.disabled'); + cy.get(QUERY_BAR).should('contain', savedQueryFilterKey); + + cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true }); + cy.get(DEFINE_CONTINUE_BUTTON).should('not.exist'); + + fillAboutRuleAndContinue(this.rule); + fillScheduleRuleAndContinue(this.rule); + cy.intercept('POST', '/api/detection_engine/rules').as('savedQueryRule'); + createAndEnableRule(); + + cy.wait('@savedQueryRule').then(({ response }) => { + // created rule should have saved_query type + cy.wrap(response?.body.type).should('equal', 'saved_query'); + }); + + goToRuleDetails(); + + cy.get(RULE_NAME_HEADER).should('contain', `${this.rule.name}`); + + cy.get(DEFINE_RULE_PANEL_PROGRESS).should('not.exist'); + + getDetails(SAVED_QUERY_NAME_DETAILS).should('contain', savedQueryName); + getDetails(SAVED_QUERY_DETAILS).should('contain', savedQueryQuery); + getDetails(SAVED_QUERY_FILTERS_DETAILS).should('contain', savedQueryFilterKey); + }); + + context('Non existent saved query', () => { + const FAILED_TO_LOAD_ERROR = 'Failed to load the saved query'; + beforeEach(() => { + createSavedQueryRule({ ...getNewRule(), savedId: 'non-existent' }); + cy.visit(SECURITY_DETECTIONS_RULES_URL); + }); + it('Shows error toast on details page when saved query can not be loaded', function () { + goToRuleDetails(); + + cy.get(TOASTER).should('contain', FAILED_TO_LOAD_ERROR); + }); + + it('Shows validation error on rule edit when saved query can not be loaded', function () { + editFirstRule(); + + cy.get(CUSTOM_QUERY_BAR).should('contain', FAILED_TO_LOAD_ERROR); + }); + }); + + context('Editing', () => { + it('Allows to update query rule as saved_query rule type', () => { + createSavedQuery(savedQueryName, savedQueryQuery); + createCustomRule(getNewRule()); + + cy.visit(SECURITY_DETECTIONS_RULES_URL); + + editFirstRule(); + + selectAndLoadSavedQuery(savedQueryName, savedQueryQuery); + checkLoadQueryDynamically(); + + cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); + saveEditedRule(); + + cy.wait('@editedRule').then(({ response }) => { + // updated rule should be saved as saved_query type once Load query dynamically checkbox was checked + cy.wrap(response?.body.type).should('equal', 'saved_query'); + }); + + cy.get(DEFINE_RULE_PANEL_PROGRESS).should('not.exist'); + + getDetails(SAVED_QUERY_NAME_DETAILS).should('contain', savedQueryName); + getDetails(SAVED_QUERY_DETAILS).should('contain', savedQueryQuery); + }); + + it('Allows to update saved_query rule as query rule type', () => { + const expectedCustomTestQuery = 'random test query'; + createSavedQuery(savedQueryName, savedQueryQuery).then((response) => { + cy.log(JSON.stringify(response.body, null, 2)); + createSavedQueryRule({ ...getNewRule(), savedId: response.body.id }); + }); + + cy.visit(SECURITY_DETECTIONS_RULES_URL); + + editFirstRule(); + + // query input should be disabled and has value of saved query + getCustomQueryInput().should('have.value', savedQueryQuery).should('be.disabled'); + + // after unchecking Load Query Dynamically checkbox, query input becomes enabled, type custom query + uncheckLoadQueryDynamically(); + getCustomQueryInput().should('be.enabled').clear().type(expectedCustomTestQuery); + + cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); + saveEditedRule(); + + cy.wait('@editedRule').then(({ response }) => { + // updated rule should be saved as query type once Load query dynamically checkbox was unchecked + cy.wrap(response?.body.type).should('equal', 'query'); + }); + + getDetails(CUSTOM_QUERY_DETAILS).should('contain', expectedCustomTestQuery); + }); + + it('Allows to update saved_query rule with non-existent query by adding custom query', () => { + const expectedCustomTestQuery = 'random test query'; + createSavedQueryRule({ ...getNewRule(), savedId: 'non-existent' }); + + cy.visit(SECURITY_DETECTIONS_RULES_URL); + + editFirstRule(); + + // type custom query, ensure Load dynamically checkbox is absent, as rule can't be saved win non valid saved query + getCustomQueryInput().type(expectedCustomTestQuery); + cy.get(LOAD_QUERY_DYNAMICALLY_CHECKBOX).should('not.visible'); + + cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); + saveEditedRule(); + + cy.wait('@editedRule').then(({ response }) => { + // updated rule should be saved as query type once Load query dynamically checkbox was unchecked + cy.wrap(response?.body.type).should('equal', 'query'); + }); + + getDetails(CUSTOM_QUERY_DETAILS).should('contain', expectedCustomTestQuery); + }); + + it('Allows to update saved_query rule with non-existent query by selecting another saved query', () => { + createSavedQuery(savedQueryName, savedQueryQuery); + createSavedQueryRule({ ...getNewRule(), savedId: 'non-existent' }); + + cy.visit(SECURITY_DETECTIONS_RULES_URL); + + editFirstRule(); + + // select another saved query, edit query input, which later should be dismissed once Load query dynamically checkbox checked + selectAndLoadSavedQuery(savedQueryName, savedQueryQuery); + getCustomQueryInput().type('AND this part wil be dismissed'); + + checkLoadQueryDynamically(); + getCustomQueryInput().should('have.value', savedQueryQuery); + + cy.intercept('PUT', '/api/detection_engine/rules').as('editedRule'); + saveEditedRule(); + + cy.wait('@editedRule').then(({ response }) => { + // updated rule type shouldn't change + cy.wrap(response?.body.type).should('equal', 'saved_query'); + }); + + cy.get(DEFINE_RULE_PANEL_PROGRESS).should('not.exist'); + + getDetails(SAVED_QUERY_NAME_DETAILS).should('contain', savedQueryName); + getDetails(SAVED_QUERY_DETAILS).should('contain', savedQueryQuery); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index 6b0051f12bc29..7b9d543e480ee 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -67,6 +67,10 @@ export interface ThresholdRule extends CustomRule { threshold: string; } +export interface SavedQueryRule extends CustomRule { + savedId: string; +} + export interface OverrideRule extends CustomRule { severityOverride: SeverityOverride[]; riskOverride: string; diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts index 4bf39a8bf1d79..e46e8a75a4341 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -62,6 +62,8 @@ export const CREATE_AND_ENABLE_BTN = '[data-test-subj="create-enable"]'; export const CUSTOM_QUERY_INPUT = '[data-test-subj="queryInput"]'; +export const CUSTOM_QUERY_BAR = '[data-test-subj="detectionEngineStepDefineRuleQueryBar"]'; + export const THREAT_MAPPING_COMBO_BOX_INPUT = '[data-test-subj="threatMatchInput"] [data-test-subj="fieldAutocompleteComboBox"]'; @@ -238,3 +240,19 @@ export const NEW_TERMS_HISTORY_SIZE = export const NEW_TERMS_HISTORY_TIME_TYPE = '[data-test-subj="detectionEngineStepDefineRuleHistoryWindowSize"] [data-test-subj="timeType"]'; + +export const LOAD_QUERY_DYNAMICALLY_CHECKBOX = + '[data-test-subj="detectionEngineStepDefineRuleShouldLoadQueryDynamically"] input'; + +export const SHOW_QUERY_BAR_BUTTON = '[data-test-subj="showQueryBarMenu"]'; + +export const QUERY_BAR = '[data-test-subj="detectionEngineStepDefineRuleQueryBar"]'; + +export const LOAD_SAVED_QUERIES_LIST_BUTTON = + '[data-test-subj="saved-query-management-load-button"]'; + +export const savedQueryByName = (savedQueryName: string) => + `[data-test-subj="load-saved-query-${savedQueryName}-button"]`; + +export const APPLY_SELECTED_SAVED_QUERY_BUTTON = + '[data-test-subj="saved-query-management-apply-changes-button"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts index 80883d825c18f..0c834c140061c 100644 --- a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts @@ -22,6 +22,14 @@ export const ANOMALY_SCORE_DETAILS = 'Anomaly score'; export const CUSTOM_QUERY_DETAILS = 'Custom query'; +export const SAVED_QUERY_NAME_DETAILS = 'Saved query name'; + +export const SAVED_QUERY_DETAILS = /^Saved query$/; + +export const SAVED_QUERY_FILTERS_DETAILS = 'Saved query filters'; + +export const FILTERS_DETAILS = 'Filters'; + export const DATA_VIEW_DETAILS = 'Data View'; export const DEFINITION_DETAILS = @@ -112,3 +120,6 @@ export const removeExternalLinkText = (str: string) => str.replace(/\(opens in a new tab or window\)/g, ''); export const BACK_TO_RULES = '[data-test-subj="ruleDetailsBackToAllRules"]'; + +export const DEFINE_RULE_PANEL_PROGRESS = + '[data-test-subj="defineRule"] [data-test-subj="stepPanelProgress"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts index afee74e1a943b..4fb076f11f445 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts @@ -11,6 +11,7 @@ import type { MachineLearningRule, ThresholdRule, NewTermsRule, + SavedQueryRule, } from '../../objects/rule'; export const createMachineLearningRule = (rule: MachineLearningRule, ruleId = 'ml_rule_testing') => @@ -149,6 +150,39 @@ export const createNewTermsRule = (rule: NewTermsRule, ruleId = 'rule_testing') } }; +export const createSavedQueryRule = ( + rule: SavedQueryRule, + ruleId = 'saved_query_rule_testing' +): Cypress.Chainable> => + cy.request({ + method: 'POST', + url: 'api/detection_engine/rules', + body: { + rule_id: ruleId, + risk_score: parseInt(rule.riskScore, 10), + description: rule.description, + interval: rule.interval, + name: rule.name, + severity: rule.severity.toLocaleLowerCase(), + type: 'saved_query', + from: 'now-50000h', + index: rule.dataSource.type === 'indexPatterns' ? rule.dataSource.index : '', + saved_id: rule.savedId, + language: 'kuery', + enabled: false, + exceptions_list: rule.exceptionLists ?? [], + tags: rule.tags, + ...(rule.timeline.id ?? rule.timeline.templateTimelineId + ? { + timeline_id: rule.timeline.id ?? rule.timeline.templateTimelineId, + timeline_title: rule.timeline.title, + } + : {}), + }, + headers: { 'kbn-xsrf': 'cypress-creds' }, + failOnStatusCode: false, + }); + export const createCustomIndicatorRule = (rule: ThreatIndicatorRule, ruleId = 'rule_testing') => { if (rule.dataSource.type === 'indexPatterns') { cy.request({ diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/saved_queries.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/saved_queries.ts new file mode 100644 index 0000000000000..8c4aea51ec045 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/saved_queries.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const createSavedQuery = ( + title: string, + query: string, + filterKey: string = 'agent.hostname' +) => + cy.request({ + method: 'POST', + url: '/api/saved_query/_create', + body: { + title, + description: '', + query: { query, language: 'kuery' }, + filters: [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: filterKey, + params: { query: 'localhost' }, + }, + query: { match_phrase: { [filterKey]: 'localhost' } }, + }, + ], + }, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts index cd9525e95b0b2..23f04d0fd3c2b 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common.ts @@ -179,6 +179,23 @@ export const deleteCases = () => { }); }; +export const deleteSavedQueries = () => { + const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; + cy.request('POST', `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, { + query: { + bool: { + filter: [ + { + match: { + type: 'query', + }, + }, + ], + }, + }, + }); +}; + export const postDataView = (dataSource: string) => { cy.request({ method: 'POST', diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index bd9c549eb0f40..2b7e8e0b3375e 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -24,6 +24,7 @@ import { ADD_REFERENCE_URL_BTN, ADVANCED_SETTINGS_BTN, ANOMALY_THRESHOLD_INPUT, + APPLY_SELECTED_SAVED_QUERY_BUTTON, AT_LEAST_ONE_INDEX_PATTERN, AT_LEAST_ONE_VALID_MATCH, BACK_TO_ALL_RULES_LINK, @@ -43,6 +44,8 @@ import { INPUT, INVALID_MATCH_CONTENT, INVESTIGATION_NOTES_TEXTAREA, + LOAD_QUERY_DYNAMICALLY_CHECKBOX, + LOAD_SAVED_QUERIES_LIST_BUTTON, LOOK_BACK_INTERVAL, LOOK_BACK_TIME_TYPE, MACHINE_LEARNING_DROPDOWN_INPUT, @@ -54,6 +57,7 @@ import { MITRE_ATTACK_TACTIC_DROPDOWN, MITRE_ATTACK_TECHNIQUE_DROPDOWN, MITRE_TACTIC, + QUERY_BAR, QUERY_PREVIEW_BUTTON, REFERENCE_URLS_INPUT, REFRESH_BUTTON, @@ -69,11 +73,13 @@ import { RULES_CREATION_PREVIEW_REFRESH_BUTTON, RUNS_EVERY_INTERVAL, RUNS_EVERY_TIME_TYPE, + savedQueryByName, SCHEDULE_CONTINUE_BUTTON, SCHEDULE_EDIT_TAB, SEVERITY_DROPDOWN, SEVERITY_MAPPING_OVERRIDE_OPTION, SEVERITY_OVERRIDE_ROW, + SHOW_QUERY_BAR_BUTTON, TAGS_INPUT, THREAT_COMBO_BOX_INPUT, THREAT_ITEM_ENTRY_DELETE_BUTTON, @@ -591,3 +597,21 @@ export const waitForTheRuleToBeExecuted = () => { .then((ruleStatus) => ruleStatus === 'succeeded'); }); }; + +export const selectAndLoadSavedQuery = (queryName: string, queryValue: string) => { + cy.get(QUERY_BAR).find(SHOW_QUERY_BAR_BUTTON).click(); + + cy.get(LOAD_SAVED_QUERIES_LIST_BUTTON).click(); + cy.get(savedQueryByName(queryName)).click(); + cy.get(APPLY_SELECTED_SAVED_QUERY_BUTTON).click(); + + cy.get(CUSTOM_QUERY_INPUT).should('have.value', queryValue); +}; + +export const checkLoadQueryDynamically = () => { + cy.get(LOAD_QUERY_DYNAMICALLY_CHECKBOX).click({ force: true }).should('be.checked'); +}; + +export const uncheckLoadQueryDynamically = () => { + cy.get(LOAD_QUERY_DYNAMICALLY_CHECKBOX).click({ force: true }).should('not.be.checked'); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts b/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts index 1cec924eae55a..2a9cf091bb0df 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts @@ -155,7 +155,7 @@ export const goBackToAllRulesTable = () => { cy.get(BACK_TO_RULES).click(); }; -export const getDetails = (title: string) => +export const getDetails = (title: string | RegExp) => cy.get(DETAILS_TITLE).contains(title).next(DETAILS_DESCRIPTION); export const hasIndexPatterns = (indexPatterns: string) => { diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json index a0061bfe195ad..5e9557dbe2c0b 100644 --- a/x-pack/plugins/security_solution/kibana.json +++ b/x-pack/plugins/security_solution/kibana.json @@ -12,6 +12,7 @@ "actions", "alerting", "cases", + "cloud", "cloudSecurityPosture", "dashboard", "data", diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts index 6ac3a0aa7a3ff..99db764285ab7 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -173,7 +173,6 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ title: ENTITY_ANALYTICS, path: ENTITY_ANALYTICS_PATH, features: [FEATURE.general], - experimentalKey: 'entityAnalyticsDashboardEnabled', isPremium: true, keywords: [ i18n.translate('xpack.securitySolution.search.entityAnalytics', { @@ -296,7 +295,6 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ defaultMessage: 'Host risk', }), path: `${HOSTS_PATH}/hostRisk`, - experimentalKey: 'riskyHostsEnabled', }, { id: SecurityPageName.sessions, @@ -386,7 +384,6 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ defaultMessage: 'User risk', }), path: `${USERS_PATH}/userRisk`, - experimentalKey: 'riskyUsersEnabled', }, { id: SecurityPageName.usersEvents, diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts b/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts index 9d74e059434fa..7da3f75ba22e5 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts @@ -14,7 +14,6 @@ import { IconExceptionLists } from '../management/icons/exception_lists'; const commonLinkProperties: Partial = { hideTimeline: true, capabilities: [`${SERVER_APP_ID}.show`], - isBeta: true, }; export const rootLinks: LinkItem = { diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.test.tsx index 0c6cf454e73ee..2320b85113c3c 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.test.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { render } from '@testing-library/react'; import { TestProviders } from '../../../mock'; -import { NO_HOST_RISK_DATA_DESCRIPTION } from './translations'; import { HostRiskSummary } from './host_risk_summary'; import { RiskSeverity } from '../../../../../common/search_strategy'; +import { getEmptyValue } from '../../empty_value'; describe('HostRiskSummary', () => { it('renders host risk data', () => { @@ -60,36 +60,7 @@ describe('HostRiskSummary', () => { expect(getByTestId('loading')).toBeInTheDocument(); }); - it('renders no host data message when module is diabled', () => { - const hostRisk = { - loading: false, - isModuleEnabled: false, - result: [ - { - '@timestamp': '1641902530', - host: { - name: 'test-host-name', - risk: { - multipliers: [], - calculated_score_norm: 9999, - calculated_level: RiskSeverity.low, - rule_risks: [], - }, - }, - }, - ], - }; - - const { getByText } = render( - - - - ); - - expect(getByText(NO_HOST_RISK_DATA_DESCRIPTION)).toBeInTheDocument(); - }); - - it('renders no host data message when there is no host data', () => { + it('renders empty value when there is no host data', () => { const hostRisk = { loading: false, isModuleEnabled: true, @@ -102,6 +73,6 @@ describe('HostRiskSummary', () => { ); - expect(getByText(NO_HOST_RISK_DATA_DESCRIPTION)).toBeInTheDocument(); + expect(getByText(getEmptyValue())).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.tsx index 970656933b938..536d77c8c8319 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.tsx @@ -6,12 +6,13 @@ */ import React from 'react'; -import { EuiLoadingSpinner, EuiPanel, EuiSpacer, EuiLink, EuiText } from '@elastic/eui'; +import { EuiLoadingSpinner, EuiPanel, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import * as i18n from './translations'; import { EnrichedDataRow, ThreatSummaryPanelHeader } from './threat_summary_view'; import { RiskScore } from '../../severity/common'; import type { HostRisk } from '../../../../risk_score/containers'; +import { getEmptyValue } from '../../empty_value'; import { RISKY_HOSTS_DOC_LINK } from '../../../../../common/constants'; const HostRiskSummaryComponent: React.FC<{ @@ -41,24 +42,19 @@ const HostRiskSummaryComponent: React.FC<{ {hostRisk.loading && } - {!hostRisk.loading && (!hostRisk.isModuleEnabled || hostRisk.result?.length === 0) && ( - <> - - - {i18n.NO_HOST_RISK_DATA_DESCRIPTION} - - - )} - - {hostRisk.isModuleEnabled && hostRisk.result && hostRisk.result.length > 0 && ( + {!hostRisk.loading && ( <> + hostRisk.result && hostRisk.result.length > 0 ? ( + + ) : ( + getEmptyValue() + ) } /> diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.test.tsx index b87c96c34632a..23e1da2dde8c0 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.test.tsx @@ -27,6 +27,14 @@ jest.mock('../../../lib/kibana', () => ({ jest.mock('../table/action_cell', () => ({ ActionCell: () => <> })); jest.mock('../table/field_name_cell'); +const RISK_SCORE_DATA_ROWS = 2; + +const EMPTY_RISK_SCORE = { + loading: false, + isModuleEnabled: true, + result: [], +}; + describe('ThreatSummaryView', () => { const eventId = '5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31'; const timelineId = 'detections-page'; @@ -38,6 +46,7 @@ describe('ThreatSummaryView', () => { buildEventEnrichmentMock({ 'matched.id': ['test.id'], 'matched.field': ['test.field'] }), buildEventEnrichmentMock({ 'matched.id': ['other.id'], 'matched.field': ['other.field'] }), ]; + const { getByText, getAllByTestId } = render( { enrichments={enrichments} eventId={eventId} timelineId={timelineId} - hostRisk={null} + hostRisk={EMPTY_RISK_SCORE} + userRisk={EMPTY_RISK_SCORE} /> ); expect(getByText('Enriched with Threat Intelligence')).toBeInTheDocument(); - expect(getAllByTestId('EnrichedDataRow')).toHaveLength(enrichments.length); + expect(getAllByTestId('EnrichedDataRow')).toHaveLength( + enrichments.length + RISK_SCORE_DATA_ROWS + ); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx index a7a371e60cf9e..866d7ae0a6c39 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx @@ -26,8 +26,9 @@ import type { TimelineEventsDetailsItem, } from '../../../../../common/search_strategy'; import { HostRiskSummary } from './host_risk_summary'; +import { UserRiskSummary } from './user_risk_summary'; import { EnrichmentSummary } from './enrichment_summary'; -import type { HostRisk } from '../../../../risk_score/containers'; +import type { HostRisk, UserRisk } from '../../../../risk_score/containers'; const UppercaseEuiTitle = styled(EuiTitle)` text-transform: uppercase; @@ -125,7 +126,8 @@ const ThreatSummaryViewComponent: React.FC<{ enrichments: CtiEnrichment[]; eventId: string; timelineId: string; - hostRisk: HostRisk | null; + hostRisk: HostRisk; + userRisk: UserRisk; isDraggable?: boolean; isReadOnly?: boolean; }> = ({ @@ -135,13 +137,10 @@ const ThreatSummaryViewComponent: React.FC<{ eventId, timelineId, hostRisk, + userRisk, isDraggable, isReadOnly, }) => { - if (!hostRisk && enrichments.length === 0) { - return null; - } - return ( <> @@ -152,11 +151,13 @@ const ThreatSummaryViewComponent: React.FC<{ - {hostRisk && ( - - - - )} + + + + + + + = ({ userRisk }) => ( + <> + + + + + ), + }} + /> + } + /> + + {userRisk.loading && } + + {!userRisk.loading && ( + <> + 0 ? ( + + ) : ( + getEmptyValue() + ) + } + /> + + )} + + +); + +export const UserRiskSummary = React.memo(UserRiskSummaryComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index 11ca04b1224de..92f4c57964e10 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -40,8 +40,8 @@ import { EnrichmentRangePicker } from './cti_details/enrichment_range_picker'; import { Reason } from './reason'; import { InvestigationGuideView } from './investigation_guide_view'; import { Overview } from './overview'; -import type { HostRisk } from '../../../risk_score/containers'; import { Insights } from './insights/insights'; +import { useRiskScoreData } from './use_risk_score_data'; type EventViewTab = EuiTabbedContentTab; @@ -67,7 +67,6 @@ interface Props { rawEventData: object | undefined; timelineTabType: TimelineTabs | 'flyout'; timelineId: string; - hostRisk: HostRisk | null; handleOnEventClosed: () => void; isReadOnly?: boolean; } @@ -111,7 +110,6 @@ const EventDetailsComponent: React.FC = ({ rawEventData, timelineId, timelineTabType, - hostRisk, handleOnEventClosed, isReadOnly, }) => { @@ -148,6 +146,8 @@ const EventDetailsComponent: React.FC = ({ const enrichmentCount = allEnrichments.length; + const { hostRisk, userRisk, isLicenseValid } = useRiskScoreData(data); + const summaryTab: EventViewTab | undefined = useMemo( () => isAlert @@ -193,10 +193,11 @@ const EventDetailsComponent: React.FC = ({ isReadOnly={isReadOnly} /> - {(enrichmentCount > 0 || hostRisk) && ( + {enrichmentCount > 0 && isLicenseValid && ( = ({ goToTableTab, handleOnEventClosed, isReadOnly, + userRisk, + isLicenseValid, ] ); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/use_risk_score_data.test.ts b/x-pack/plugins/security_solution/public/common/components/event_details/use_risk_score_data.test.ts new file mode 100644 index 0000000000000..4dee4aeb3008d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/use_risk_score_data.test.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { TestProviders } from '../../mock'; +import { ONLY_FIRST_ITEM_PAGINATION, useRiskScoreData } from './use_risk_score_data'; +import { useUserRiskScore, useHostRiskScore } from '../../../risk_score/containers'; +import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; + +const mockUseUserRiskScore = useUserRiskScore as jest.Mock; +const mockUseHostRiskScore = useHostRiskScore as jest.Mock; +const mockUseBasicDataFromDetailsData = useBasicDataFromDetailsData as jest.Mock; +jest.mock('../../../risk_score/containers'); +jest.mock('../../../timelines/components/side_panel/event_details/helpers'); +const defaultResult = { + data: [], + inspect: {}, + isInspected: false, + isLicenseValid: true, + isModuleEnabled: true, + refetch: () => {}, + totalCount: 0, +}; +const defaultRisk = { + loading: false, + isModuleEnabled: true, + result: [], +}; + +const defaultArgs = [ + { + field: 'host.name', + isObjectArray: false, + }, +]; + +describe('useRiskScoreData', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockUseUserRiskScore.mockReturnValue([false, defaultResult]); + mockUseHostRiskScore.mockReturnValue([false, defaultResult]); + mockUseBasicDataFromDetailsData.mockReturnValue({ + hostName: 'host', + userName: 'user', + }); + }); + test('returns expected default values', () => { + const { result } = renderHook(() => useRiskScoreData(defaultArgs), { + wrapper: TestProviders, + }); + expect(result.current).toEqual({ + hostRisk: defaultRisk, + userRisk: defaultRisk, + isLicenseValid: true, + }); + }); + + test('builds filter query for risk score hooks', () => { + renderHook(() => useRiskScoreData(defaultArgs), { + wrapper: TestProviders, + }); + expect(mockUseUserRiskScore).toHaveBeenCalledWith({ + filterQuery: { terms: { 'user.name': ['user'] } }, + pagination: ONLY_FIRST_ITEM_PAGINATION, + skip: false, + }); + expect(mockUseHostRiskScore).toHaveBeenCalledWith({ + filterQuery: { terms: { 'host.name': ['host'] } }, + pagination: ONLY_FIRST_ITEM_PAGINATION, + skip: false, + }); + }); + + test('skips risk score hooks with no entity name', () => { + mockUseBasicDataFromDetailsData.mockReturnValue({ hostName: undefined, userName: undefined }); + renderHook(() => useRiskScoreData(defaultArgs), { + wrapper: TestProviders, + }); + expect(mockUseUserRiskScore).toHaveBeenCalledWith({ + filterQuery: undefined, + pagination: ONLY_FIRST_ITEM_PAGINATION, + skip: true, + }); + expect(mockUseHostRiskScore).toHaveBeenCalledWith({ + filterQuery: undefined, + pagination: ONLY_FIRST_ITEM_PAGINATION, + skip: true, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/use_risk_score_data.ts b/x-pack/plugins/security_solution/public/common/components/event_details/use_risk_score_data.ts new file mode 100644 index 0000000000000..eb462671afd94 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/use_risk_score_data.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 { useMemo } from 'react'; +import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; +import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; +import { buildHostNamesFilter, buildUserNamesFilter } from '../../../../common/search_strategy'; +import type { HostRisk, UserRisk } from '../../../risk_score/containers'; +import { useUserRiskScore, useHostRiskScore } from '../../../risk_score/containers'; + +export const ONLY_FIRST_ITEM_PAGINATION = { + cursorStart: 0, + querySize: 1, +}; + +export const useRiskScoreData = (data: TimelineEventsDetailsItem[]) => { + const { hostName, userName } = useBasicDataFromDetailsData(data); + + const hostNameFilterQuery = useMemo( + () => (hostName ? buildHostNamesFilter([hostName]) : undefined), + [hostName] + ); + + const [ + hostRiskLoading, + { + data: hostRiskData, + isLicenseValid: isHostLicenseValid, + isModuleEnabled: isHostRiskModuleEnabled, + }, + ] = useHostRiskScore({ + filterQuery: hostNameFilterQuery, + pagination: ONLY_FIRST_ITEM_PAGINATION, + skip: !hostNameFilterQuery, + }); + + const hostRisk: HostRisk = useMemo( + () => ({ + loading: hostRiskLoading, + isModuleEnabled: isHostRiskModuleEnabled, + result: hostRiskData, + }), + [hostRiskData, hostRiskLoading, isHostRiskModuleEnabled] + ); + + const userNameFilterQuery = useMemo( + () => (userName ? buildUserNamesFilter([userName]) : undefined), + [userName] + ); + + const [ + userRiskLoading, + { + data: userRiskData, + isLicenseValid: isUserLicenseValid, + isModuleEnabled: isUserRiskModuleEnabled, + }, + ] = useUserRiskScore({ + filterQuery: userNameFilterQuery, + pagination: ONLY_FIRST_ITEM_PAGINATION, + skip: !userNameFilterQuery, + }); + + const userRisk: UserRisk = useMemo( + () => ({ + loading: userRiskLoading, + isModuleEnabled: isUserRiskModuleEnabled, + result: userRiskData, + }), + [userRiskLoading, isUserRiskModuleEnabled, userRiskData] + ); + + return { userRisk, hostRisk, isLicenseValid: isHostLicenseValid && isUserLicenseValid }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/title.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/title.tsx index 0353d4f76c1c9..0f54c6f579a9f 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/title.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_page/title.tsx @@ -59,6 +59,7 @@ const TitleComponent: React.FC = ({ draggableArguments, title, badgeOptio label={badgeOptions.text} tooltipContent={badgeOptions.tooltip} tooltipPosition="bottom" + size={badgeOptions.size} /> ) : ( diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/types.ts b/x-pack/plugins/security_solution/public/common/components/header_page/types.ts index 358a8118e5cc3..698178480045d 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/header_page/types.ts @@ -6,6 +6,7 @@ */ import type { EuiBadgeProps } from '@elastic/eui'; +import type { BetaBadgeSize } from '@elastic/eui/src/components/badge/beta_badge/beta_badge'; import type React from 'react'; export type TitleProp = string | React.ReactNode; @@ -19,4 +20,5 @@ export interface BadgeOptions { text: React.ReactNode; tooltip?: React.ReactNode; color?: EuiBadgeProps['color']; + size?: BetaBadgeSize; } diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/nav_item_beta_badge.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/nav_item_beta_badge.tsx index ddccbee1546d3..bef5ba4d8362d 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/nav_item_beta_badge.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/nav_item_beta_badge.tsx @@ -10,12 +10,12 @@ import { css } from '@emotion/react'; import { EuiBetaBadge, useEuiTheme } from '@elastic/eui'; import { BETA } from '../../translations'; -export const NavItemBetaBadge = () => { +export const NavItemBetaBadge = ({ text }: { text?: string }) => { const { euiTheme } = useEuiTheme(); return ( ...(link.landingImage != null ? { image: link.landingImage } : {}), ...(link.skipUrlState != null ? { skipUrlState: link.skipUrlState } : {}), ...(link.isBeta != null ? { isBeta: link.isBeta } : {}), + ...(link.betaOptions != null ? { betaOptions: link.betaOptions } : {}), ...(link.links && link.links.length ? { links: formatNavLinkItems(link.links), diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx index 4b7c58ce2ba0b..ce239c34eae4a 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx @@ -78,6 +78,7 @@ const useFormatSideNavItem = (): FormatSideNavItems => { label: current.title, description: current.description, isBeta: current.isBeta, + betaOptions: current.betaOptions, ...getSecuritySolutionLinkProps({ deepLinkId: current.id }), }); } diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx index 4516f103f9167..2c7dbf4074983 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx @@ -156,7 +156,7 @@ const SolutionNavPanelCategories: React.FC = ({ const SolutionNavPanelItems: React.FC = ({ items, onClose }) => ( <> - {items.map(({ id, href, onClick, label, description, isBeta }) => ( + {items.map(({ id, href, onClick, label, description, isBeta, betaOptions }) => ( = ({ items, on }} > {label} - {isBeta && } + {isBeta && } {description} diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/types.ts index f9e186930e247..f47e1755cc796 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/types.ts @@ -18,6 +18,9 @@ export interface DefaultSideNavItem { items?: DefaultSideNavItem[]; categories?: LinkCategories; isBeta?: boolean; + betaOptions?: { + text: string; + }; } export interface CustomSideNavItem { diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx index d364a5526a226..abc4350eb83fa 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx @@ -23,6 +23,7 @@ const TabNavigationItemComponent = ({ name, isSelected, isBeta, + betaOptions, }: TabNavigationItemProps) => { const { getAppUrl, navigateTo } = useNavigation(); @@ -47,7 +48,7 @@ const TabNavigationItemComponent = ({ isSelected={isSelected} href={appHref} onClick={handleClick} - append={isBeta && } + append={isBeta && } > {name} @@ -92,6 +93,7 @@ export const TabNavigationComponent: React.FC = ({ navTabs, disabled={tab.disabled} isSelected={isSelected} isBeta={tab.isBeta} + betaOptions={tab.betaOptions} /> ); }), diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts index fa8d0a0e3fa17..2e687d09f290e 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts @@ -21,4 +21,7 @@ export interface TabNavigationItemProps { name: string; isSelected: boolean; isBeta?: boolean; + betaOptions?: { + text: string; + }; } diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts index 5a4c346be2e12..84341f5321169 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts @@ -62,6 +62,9 @@ export interface NavTab { urlKey?: UrlStateType; pageId?: SecurityPageName; isBeta?: boolean; + betaOptions?: { + text: string; + }; } export const securityNavKeys = [ SecurityPageName.alerts, @@ -113,4 +116,7 @@ export interface NavLinkItem { title: string; skipUrlState?: boolean; isBeta?: boolean; + betaOptions?: { + text: string; + }; } diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx index 2a8d977760cbf..83a8e97f0a65e 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx @@ -77,9 +77,7 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record) { const isPolicyListEnabled = useIsExperimentalFeatureEnabled('policyListEnabled'); const mlCapabilities = useMlCapabilities(); const hasMlPermissions = hasMlLicense(mlCapabilities) && hasMlUserPermissions(mlCapabilities); - const isEntityAnalyticsDashboardEnabled = useIsExperimentalFeatureEnabled( - 'entityAnalyticsDashboardEnabled' - ); + const uiCapabilities = useKibana().services.application.capabilities; return useMemo( () => @@ -99,9 +97,7 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record) { ...(navTabs[SecurityPageName.kubernetes] != null ? [navTabs[SecurityPageName.kubernetes]] : []), - ...(isEntityAnalyticsDashboardEnabled && hasMlPermissions - ? [navTabs[SecurityPageName.entityAnalytics]] - : []), + ...(hasMlPermissions ? [navTabs[SecurityPageName.entityAnalytics]] : []), ], }, { @@ -163,7 +159,6 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record) { [ uiCapabilities.siem.show, navTabs, - isEntityAnalyticsDashboardEnabled, hasCasesReadPermissions, canSeeHostIsolationExceptions, isPolicyListEnabled, diff --git a/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx index e487277ff9a7a..82080ba1ac602 100644 --- a/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/query_bar/index.tsx @@ -33,6 +33,7 @@ export interface QueryBarComponentProps { savedQuery?: SavedQuery; onSavedQuery: (savedQuery: SavedQuery | undefined) => void; displayStyle?: SearchBarProps['displayStyle']; + isDisabled?: boolean; } export const QueryBar = memo( @@ -53,6 +54,7 @@ export const QueryBar = memo( onSavedQuery, dataTestSubj, displayStyle, + isDisabled, }) => { const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { @@ -130,6 +132,7 @@ export const QueryBar = memo( dataTestSubj={dataTestSubj} savedQuery={savedQuery} displayStyle={displayStyle} + isDisabled={isDisabled} /> ); } diff --git a/x-pack/plugins/security_solution/public/common/components/risk_score_deprecated/index.tsx b/x-pack/plugins/security_solution/public/common/components/risk_score_deprecated/index.tsx new file mode 100644 index 0000000000000..745e95a361342 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/risk_score_deprecated/index.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { EuiButton, EuiEmptyPrompt, EuiPanel, EuiToolTip } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { RiskEntity } from '../../../risk_score/containers/feature_status/api'; +import { useCheckSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_check_signal_index'; +import { HeaderSection } from '../header_section'; +import * as i18n from './translations'; + +export const RiskScoresDeprecated = ({ entityType }: { entityType: RiskEntity }) => { + const { signalIndexExists } = useCheckSignalIndex(); + + const translations = useMemo( + () => ({ + body: i18n.UPGRADE_RISK_SCORE_DESCRIPTION, + signal: !signalIndexExists ? i18n.ENABLE_RISK_SCORE_POPOVER : null, + ...(entityType === RiskEntity.host + ? { + header: i18n.HOST_RISK_TITLE, + cta: i18n.UPGRADE_HOST_RISK_SCORE, + } + : { + header: i18n.USER_RISK_TITLE, + cta: i18n.UPGRADE_USER_RISK_SCORE, + }), + }), + [entityType, signalIndexExists] + ); + + return ( + + {translations.header}} titleSize="s" /> + {translations.cta}} + body={translations.body} + actions={ + + alert('Angela do the upgrade')} + isDisabled={!signalIndexExists} + data-test-subj={`upgrade_${entityType}_risk_score`} + > + {translations.cta} + + + } + /> + + ); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/risk_score_deprecated/translations.ts b/x-pack/plugins/security_solution/public/common/components/risk_score_deprecated/translations.ts new file mode 100644 index 0000000000000..6c0f6eb199530 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/risk_score_deprecated/translations.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const HOST_RISK_TITLE = i18n.translate('xpack.securitySolution.riskDeprecated.hosts.title', { + defaultMessage: 'Host Risk Score', +}); + +export const USER_RISK_TITLE = i18n.translate('xpack.securitySolution.riskDeprecated.users.title', { + defaultMessage: 'User Risk Scores', +}); + +export const UPGRADE_HOST_RISK_SCORE = i18n.translate( + 'xpack.securitySolution.riskDeprecated.hosts.upgradeHostRiskScore', + { + defaultMessage: 'Upgrade Host Risk Score', + } +); + +export const UPGRADE_USER_RISK_SCORE = i18n.translate( + 'xpack.securitySolution.riskDeprecated.users.upgradeUserRiskScore', + { + defaultMessage: 'Upgrade User Risk Score', + } +); + +export const UPGRADE_RISK_SCORE_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.riskDeprecated.entity.upgradeHostRiskScoreDescription', + { + defaultMessage: + 'Current data is no longer supported. Please migrate your data and upgrade the module.', + } +); + +export const ENABLE_RISK_SCORE_POPOVER = i18n.translate( + 'xpack.securitySolution.riskDeprecated.entity.enableRiskScorePopoverTitle', + { + defaultMessage: 'Alerts need to be available before upgrading module.', + } +); diff --git a/x-pack/plugins/security_solution/public/common/hooks/translations.ts b/x-pack/plugins/security_solution/public/common/hooks/translations.ts index 54ed3a79d017f..1f939282b5c72 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/translations.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/translations.ts @@ -39,3 +39,10 @@ export const EQL_TIME_INTERVAL_NOT_DEFINED = i18n.translate( defaultMessage: 'Time intervals are not defined.', } ); + +export const SAVED_QUERY_LOAD_ERROR_TOAST = i18n.translate( + 'xpack.securitySolution.hooks.useGetSavedQuery.errorToastMessage', + { + defaultMessage: 'Failed to load the saved query', + } +); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_fetch/request_names.ts b/x-pack/plugins/security_solution/public/common/hooks/use_fetch/request_names.ts index cadfd2a68fa32..5aa4cbb9f3294 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_fetch/request_names.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_fetch/request_names.ts @@ -9,6 +9,8 @@ import { APP_UI_ID } from '../../../../common/constants'; export const REQUEST_NAMES = { SECURITY_DASHBOARDS: `${APP_UI_ID} fetch security dashboards`, ANOMALIES_TABLE: `${APP_UI_ID} fetch anomalies table data`, + GET_RISK_SCORE_DEPRECATED: `${APP_UI_ID} fetch is risk score deprecated`, + GET_SAVED_QUERY: `${APP_UI_ID} fetch saved query`, } as const; export type RequestName = typeof REQUEST_NAMES[keyof typeof REQUEST_NAMES]; diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_get_saved_query.ts b/x-pack/plugins/security_solution/public/common/hooks/use_get_saved_query.ts new file mode 100644 index 0000000000000..932fe61e2ce47 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_get_saved_query.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect, useMemo } from 'react'; + +import { useSavedQueryServices } from '../utils/saved_query_services'; +import type { DefineStepRule } from '../../detections/pages/detection_engine/rules/types'; + +import { useFetch, REQUEST_NAMES } from './use_fetch'; +import { SAVED_QUERY_LOAD_ERROR_TOAST } from './translations'; +import { useAppToasts } from './use_app_toasts'; + +export const useGetSavedQuery = (savedQueryId: string | undefined) => { + const savedQueryServices = useSavedQueryServices(); + const { addError } = useAppToasts(); + const { fetch, data, isLoading, error } = useFetch( + REQUEST_NAMES.GET_SAVED_QUERY, + savedQueryServices.getSavedQuery + ); + + useEffect(() => { + if (savedQueryId) { + fetch(savedQueryId); + } + }, [savedQueryId, fetch]); + + useEffect(() => { + if (error) { + addError(error, { title: SAVED_QUERY_LOAD_ERROR_TOAST }); + } + }, [error, addError]); + + const savedQueryBar = useMemo( + () => + data + ? { + saved_id: data.id, + filters: data.attributes.filters ?? [], + query: data.attributes.query, + title: data.attributes.title, + } + : null, + [data] + ); + + return { + isSavedQueryLoading: isLoading, + savedQueryBar, + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/links/types.ts b/x-pack/plugins/security_solution/public/common/links/types.ts index 210a5f4a59754..f9a2c5776262f 100644 --- a/x-pack/plugins/security_solution/public/common/links/types.ts +++ b/x-pack/plugins/security_solution/public/common/links/types.ts @@ -84,6 +84,12 @@ export interface LinkItem { * Displays the "Beta" badge. Defaults to false. */ isBeta?: boolean; + /** + * Customize the "Beta" badge content. + */ + betaOptions?: { + text: string; + }; /** * Icon that is displayed on menu navigation landing page. * Only required for pages that are displayed inside a landing page. diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx index 0a1a4203bad57..9819ee09dce1e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx @@ -179,6 +179,7 @@ describe('helpers', () => { ...mockQueryBar, query: '', filters: [], + title: 'test title', }; const result: ListItems[] = buildQueryBarDescription({ field: 'queryBar', @@ -186,9 +187,10 @@ describe('helpers', () => { filterManager: mockFilterManager, query: mockQueryBarWithSavedId.query, savedId: mockQueryBarWithSavedId.saved_id, + savedQueryName: mockQueryBarWithSavedId.title, }); - expect(result[0].title).toEqual(<>{i18n.SAVED_ID_LABEL} ); - expect(result[0].description).toEqual(<>{mockQueryBarWithSavedId.saved_id} ); + expect(result[0].title).toEqual(<>{i18n.SAVED_QUERY_NAME_LABEL} ); + expect(result[0].description).toEqual(<>{mockQueryBarWithSavedId.title} ); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx index 8de70a8d36c10..fa432af4a9603 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx @@ -78,16 +78,28 @@ export const buildQueryBarDescription = ({ filterManager, query, savedId, + savedQueryName, indexPatterns, queryLabel, }: BuildQueryBarDescription): ListItems[] => { let items: ListItems[] = []; + const isLoadedFromSavedQuery = !isEmpty(savedId) && !isEmpty(savedQueryName); + if (isLoadedFromSavedQuery) { + items = [ + ...items, + { + title: <>{i18n.SAVED_QUERY_NAME_LABEL} , + description: <>{savedQueryName} , + }, + ]; + } + if (!isEmpty(filters)) { filterManager.setFilters(filters); items = [ ...items, { - title: <>{i18n.FILTERS_LABEL} , + title: <>{isLoadedFromSavedQuery ? i18n.SAVED_QUERY_FILTERS_LABEL : i18n.FILTERS_LABEL} , description: ( {filterManager.getFilters().map((filter, index) => ( @@ -114,20 +126,14 @@ export const buildQueryBarDescription = ({ items = [ ...items, { - title: <>{queryLabel ?? i18n.QUERY_LABEL}, + title: ( + <>{isLoadedFromSavedQuery ? i18n.SAVED_QUERY_LABEL : queryLabel ?? i18n.QUERY_LABEL} + ), description: {query}, }, ]; } - if (!isEmpty(savedId)) { - items = [ - ...items, - { - title: <>{i18n.SAVED_ID_LABEL} , - description: <>{savedId} , - }, - ]; - } + return items; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx index da714ae86cf29..ba0359c4fda3e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx @@ -176,12 +176,14 @@ export const getDescriptionItem = ( const filters = addFilterStateIfNotThere(get('queryBar.filters', data) ?? []); const query = get('queryBar.query.query', data); const savedId = get('queryBar.saved_id', data); + const savedQueryName = get('queryBar.title', data); return buildQueryBarDescription({ field, filters, filterManager, query, savedId, + savedQueryName, indexPatterns, }); } else if (field === 'eqlOptions') { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/translations.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/translations.tsx index eea283c93cfe4..9917f34ac9bf8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/translations.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/translations.tsx @@ -28,13 +28,27 @@ export const THREAT_QUERY_LABEL = i18n.translate( } ); -export const SAVED_ID_LABEL = i18n.translate( +export const SAVED_QUERY_NAME_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.savedIdLabel', { defaultMessage: 'Saved query name', } ); +export const SAVED_QUERY_FILTERS_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.savedQueryFiltersLabel', + { + defaultMessage: 'Saved query filters', + } +); + +export const SAVED_QUERY_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.savedQueryLabel', + { + defaultMessage: 'Saved query', + } +); + export const ML_TYPE_DESCRIPTION = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.mlRuleTypeDescription', { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts index 70baf0f4f4b3b..3ae093de6d451 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/types.ts @@ -23,6 +23,7 @@ export interface BuildQueryBarDescription { savedId: string; indexPatterns?: DataViewBase; queryLabel?: string; + savedQueryName?: string; } export interface BuildThreatDescription { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx index 97d7ee804293f..6d96695d3c791 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx @@ -8,7 +8,6 @@ import { EuiFormRow, EuiMutationObserver } from '@elastic/eui'; import React, { useCallback, useEffect, useState } from 'react'; import { Subscription } from 'rxjs'; -import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; import type { DataViewBase, Filter, Query } from '@kbn/es-query'; import type { SavedQuery } from '@kbn/data-plugin/public'; @@ -32,8 +31,9 @@ export interface FieldValueQueryBar { filters: Filter[]; query: Query; saved_id: string | null; + title?: string; } -interface QueryBarDefineRuleProps { +export interface QueryBarDefineRuleProps { browserFields: BrowserFields; dataTestSubj: string; field: FieldHook; @@ -44,13 +44,33 @@ interface QueryBarDefineRuleProps { openTimelineSearch: boolean; resizeParentContainer?: (height: number) => void; onValidityChange?: (arg: boolean) => void; + isDisabled?: boolean; + /** + * if saved query selected, reset query and filters to saved query values + */ + resetToSavedQuery?: boolean; + /** + * called when fetching of saved query fails + */ + onSavedQueryError?: () => void; } const actionTimelineToHide: ActionTimelineToShow[] = ['duplicate', 'createFrom']; -const StyledEuiFormRow = styled(EuiFormRow)``; +const getFieldValueFromEmptySavedQuery = () => ({ + filters: [], + query: { + query: '', + language: 'kuery', + }, + saved_id: null, +}); -// TODO need to add disabled in the SearchBar +const savedQueryToFieldValue = (savedQuery: SavedQuery): FieldValueQueryBar => ({ + filters: savedQuery.attributes.filters ?? [], + query: savedQuery.attributes.query, + saved_id: savedQuery.id, +}); export const QueryBarDefineRule = ({ browserFields, @@ -63,11 +83,15 @@ export const QueryBarDefineRule = ({ openTimelineSearch = false, resizeParentContainer, onValidityChange, + isDisabled, + resetToSavedQuery, + onSavedQueryError, }: QueryBarDefineRuleProps) => { const { value: fieldValue, setValue: setFieldValue } = field as FieldHook; const [originalHeight, setOriginalHeight] = useState(-1); const [loadingTimeline, setLoadingTimeline] = useState(false); const [savedQuery, setSavedQuery] = useState(undefined); + const [isSavedQueryFailedToLoad, setIsSavedQueryFailedToLoad] = useState(false); const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); const { uiSettings } = useKibana().services; @@ -126,8 +150,10 @@ export const QueryBarDefineRule = ({ if (isSubscribed && mySavedQuery != null) { setSavedQuery(mySavedQuery); } + setIsSavedQueryFailedToLoad(false); } catch { setSavedQuery(undefined); + setIsSavedQueryFailedToLoad(true); } } else if (savedId == null && savedQuery != null) { setSavedQuery(undefined); @@ -137,7 +163,28 @@ export const QueryBarDefineRule = ({ return () => { isSubscribed = false; }; - }, [fieldValue, filterManager, savedQuery, savedQueryServices]); + }, [ + fieldValue, + filterManager, + savedQuery, + savedQueryServices, + setIsSavedQueryFailedToLoad, + setFieldValue, + ]); + + useEffect(() => { + if (isSavedQueryFailedToLoad) { + onSavedQueryError?.(); + } + }, [onSavedQueryError, isSavedQueryFailedToLoad]); + + // if saved query fetched, reset values in queryBar input and filters to saved query's values + useEffect(() => { + if (resetToSavedQuery && savedQuery) { + const newFiledValue = savedQueryToFieldValue(savedQuery); + setFieldValue(newFiledValue); + } + }, [resetToSavedQuery, savedQuery, setFieldValue]); const onSubmitQuery = useCallback( (newQuery: Query) => { @@ -153,37 +200,30 @@ export const QueryBarDefineRule = ({ (newQuery: Query) => { const { query } = fieldValue; if (!deepEqual(query, newQuery)) { - setFieldValue({ ...fieldValue, query: newQuery }); + // if saved query failed to load, delete saved_id, when user types custom query + const savedId = isSavedQueryFailedToLoad ? null : fieldValue.saved_id; + + setFieldValue({ ...fieldValue, query: newQuery, saved_id: savedId }); } }, - [fieldValue, setFieldValue] + [fieldValue, setFieldValue, isSavedQueryFailedToLoad] ); const onSavedQuery = useCallback( (newSavedQuery: SavedQuery | undefined) => { if (newSavedQuery != null) { const { saved_id: savedId } = fieldValue; + setIsSavedQueryFailedToLoad(false); + setSavedQuery(newSavedQuery); if (newSavedQuery.id !== savedId) { - setSavedQuery(newSavedQuery); - setFieldValue({ - filters: newSavedQuery.attributes.filters ?? [], - query: newSavedQuery.attributes.query, - saved_id: newSavedQuery.id, - }); + const newFiledValue = savedQueryToFieldValue(newSavedQuery); + setFieldValue(newFiledValue); } else { - setSavedQuery(newSavedQuery); - setFieldValue({ - filters: [], - query: { - query: '', - language: 'kuery', - }, - saved_id: null, - }); + setFieldValue(getFieldValueFromEmptySavedQuery()); } } }, - [fieldValue, setFieldValue] + [fieldValue, setFieldValue, setIsSavedQueryFailedToLoad] ); const onCloseTimelineModal = useCallback(() => { @@ -241,7 +281,7 @@ export const QueryBarDefineRule = ({ return ( <> -
)} - + {openTimelineSearch ? ( { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index b2a3075928fee..e48398fbf4ff5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -39,6 +39,7 @@ import { import type { DefineStepRule, RuleStepProps } from '../../../pages/detection_engine/rules/types'; import { RuleStep, DataSourceType } from '../../../pages/detection_engine/rules/types'; import { StepRuleDescription } from '../description_step'; +import type { QueryBarDefineRuleProps } from '../query_bar'; import { QueryBarDefineRule } from '../query_bar'; import { SelectRuleType } from '../select_rule_type'; import { AnomalyThresholdSlider } from '../anomaly_threshold_slider'; @@ -63,6 +64,7 @@ import { isNewTermsRule, isThreatMatchRule, isThresholdRule, + isQueryRule, } from '../../../../../common/detection_engine/utils'; import { EqlQueryBar } from '../eql_query_bar'; import { DataViewSelector } from '../data_view_selector'; @@ -156,6 +158,7 @@ const StepDefineRuleComponent: FC = ({ 'dataSourceType', 'newTermsFields', 'historyWindowSize', + 'shouldLoadQueryDynamically', ], onChange: (data: DefineStepRule) => { if (onRuleDataChange) { @@ -176,6 +179,7 @@ const StepDefineRuleComponent: FC = ({ machineLearningJobId: formMachineLearningJobId, dataSourceType: formDataSourceType, newTermsFields: formNewTermsFields, + shouldLoadQueryDynamically: formShouldLoadQueryDynamically, } = formData; const [isQueryBarValid, setIsQueryBarValid] = useState(false); @@ -360,6 +364,14 @@ const StepDefineRuleComponent: FC = ({ } }, [ruleType, previousRuleType, getFields]); + // if saved query failed to load: + // - reset shouldLoadFormDynamically to false, as non existent query cannot be used for loading and execution + const handleSavedQueryError = useCallback(() => { + if (!isQueryBarValid) { + form.setFieldValue('shouldLoadQueryDynamically', false); + } + }, [isQueryBarValid, form]); + const handleSubmit = useCallback(() => { if (onSubmit) { onSubmit(); @@ -584,24 +596,28 @@ const StepDefineRuleComponent: FC = ({ {i18n.IMPORT_TIMELINE_QUERY} ), }} component={QueryBarDefineRule} - componentProps={{ - browserFields, - // runtimeMappings, - idAria: 'detectionEngineStepDefineRuleQueryBar', - indexPattern, - isDisabled: isLoading, - isLoading: isIndexPatternLoading, - dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', - openTimelineSearch, - onValidityChange: setIsQueryBarValid, - onCloseTimelineSearch: handleCloseTimelineSearch, - }} + componentProps={ + { + browserFields, + idAria: 'detectionEngineStepDefineRuleQueryBar', + indexPattern, + isDisabled: isLoading || formShouldLoadQueryDynamically, + resetToSavedQuery: formShouldLoadQueryDynamically, + isLoading: isIndexPatternLoading, + dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', + openTimelineSearch, + onValidityChange: setIsQueryBarValid, + onCloseTimelineSearch: handleCloseTimelineSearch, + onSavedQueryError: handleSavedQueryError, + } as QueryBarDefineRuleProps + } /> ), [ @@ -612,6 +628,8 @@ const StepDefineRuleComponent: FC = ({ isIndexPatternLoading, isLoading, openTimelineSearch, + formShouldLoadQueryDynamically, + handleSavedQueryError, ] ); const onOptionsChange = useCallback((field: FieldsEqlOptions, value: string | undefined) => { @@ -718,6 +736,29 @@ const StepDefineRuleComponent: FC = ({ )} + + {isQueryRule(ruleType) && ( + <> + + + + + + )} + <> = { @@ -127,7 +128,7 @@ export const schema: FormSchema = { ...args: Parameters ): ReturnType> | undefined => { const [{ value, path, formData }] = args; - const { query, filters } = value as FieldValueQueryBar; + const { query, filters, saved_id: savedId } = value as FieldValueQueryBar; const needsValidation = !isMlRule(formData.ruleType); if (!needsValidation) { return undefined; @@ -136,6 +137,9 @@ export const schema: FormSchema = { if (!isFieldEmpty) { return undefined; } + if (savedId) { + return { code: 'ERR_FIELD_MISSING', path, message: SAVED_QUERY_REQUIRED }; + } const message = isEqlRule(formData.ruleType) ? EQL_QUERY_REQUIRED : CUSTOM_QUERY_REQUIRED; return { code: 'ERR_FIELD_MISSING', path, message }; }, @@ -623,4 +627,14 @@ export const schema: FormSchema = { } ), }, + shouldLoadQueryDynamically: { + type: FIELD_TYPES.CHECKBOX, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldShouldLoadQueryDynamicallyLabel', + { + defaultMessage: 'Load the saved query dynamically on each rule execution', + } + ), + defaultValue: false, + }, }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx index 458ab73dd1bb8..b28d06777fd3c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx @@ -14,6 +14,13 @@ export const CUSTOM_QUERY_REQUIRED = i18n.translate( } ); +export const SAVED_QUERY_REQUIRED = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.savedQueryFieldRequiredError', + { + defaultMessage: 'Failed to load the saved query. Select a new one or add a custom query.', + } +); + export const EQL_QUERY_REQUIRED = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.eqlQueryFieldRequiredError', { @@ -71,6 +78,13 @@ export const EQL_QUERY_BAR_LABEL = i18n.translate( } ); +export const SAVED_QUERY_CHECKBOX_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.SavedQueryCheckboxLabel', + { + defaultMessage: 'Saved query', + } +); + export const THREAT_MATCH_INDEX_HELPER_TEXT = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.threatMatchingIcesHelperDescription', { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_panel/index.tsx index ac9a153ad76bf..0eb9e2cbf535d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_panel/index.tsx @@ -25,7 +25,14 @@ MyPanel.displayName = 'MyPanel'; const StepPanelComponent: React.FC = ({ children, loading, title }) => ( - {loading && } + {loading && ( + + )} {children} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts index fdf1370587638..967ba8a90d4f4 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts @@ -220,6 +220,7 @@ export const mockDefineStepRule = (): DefineStepRule => ({ dataSourceType: DataSourceType.IndexPatterns, newTermsFields: ['host.ip'], historyWindowSize: '7d', + shouldLoadQueryDynamically: false, }); export const mockScheduleStepRule = (): ScheduleStepRule => ({ diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts index ae56e6a525508..2ab0c7ae0b5b2 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts @@ -118,9 +118,8 @@ describe('helpers', () => { language: 'kuery', filters: mockQueryBar.filters, query: 'test query', - saved_id: 'test123', index: ['filebeat-'], - type: 'saved_query', + type: 'query', timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', timeline_title: 'Titled timeline', }; @@ -128,30 +127,109 @@ describe('helpers', () => { expect(result).toEqual(expected); }); - test('returns formatted object with no saved_id if no savedId provided', () => { - const mockStepData: DefineStepRule = { - ...mockData, - queryBar: { - ...mockData.queryBar, - saved_id: '', - }, - }; - const result = formatDefineStepData(mockStepData); - const expected: DefineStepRuleJson = { - language: 'kuery', - filters: mockQueryBar.filters, - query: 'test query', - index: ['filebeat-'], - saved_id: '', - type: 'query', - timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', - timeline_title: 'Titled timeline', - }; + describe('saved_query and query rule types', () => { + test('returns query rule if savedId provided but shouldLoadQueryDynamically != true', () => { + const mockStepData: DefineStepRule = { + ...mockData, + queryBar: { + ...mockData.queryBar, + saved_id: 'mock-test-id', + }, + ruleType: 'query', + }; + const result = formatDefineStepData(mockStepData); - expect(result).toEqual(expected); + expect(result.saved_id).toBeUndefined(); + expect(result.type).toBe('query'); + expect(result.query).toBe('test query'); + }); + + test('returns query rule if shouldLoadQueryDynamically = true and savedId not provided for rule type query', () => { + const mockStepData: DefineStepRule = { + ...mockData, + queryBar: { + ...mockData.queryBar, + saved_id: null, + }, + ruleType: 'query', + shouldLoadQueryDynamically: true, + }; + const result = formatDefineStepData(mockStepData); + + expect(result.saved_id).toBeUndefined(); + expect(result.type).toBe('query'); + expect(result.query).toBe('test query'); + }); + + test('returns query rule if shouldLoadQueryDynamically = true and savedId not provided for rule type saved_query', () => { + const mockStepData: DefineStepRule = { + ...mockData, + queryBar: { + ...mockData.queryBar, + saved_id: null, + }, + ruleType: 'saved_query', + shouldLoadQueryDynamically: true, + }; + const result = formatDefineStepData(mockStepData); + + expect(result.saved_id).toBeUndefined(); + expect(result.type).toBe('query'); + expect(result.query).toBe('test query'); + }); + + test('returns query rule type if savedId provided but shouldLoadQueryDynamically != true and rule type is saved_query', () => { + const mockStepData: DefineStepRule = { + ...mockData, + queryBar: { + ...mockData.queryBar, + saved_id: 'mock-test-id', + }, + ruleType: 'saved_query', + }; + const result = formatDefineStepData(mockStepData); + + expect(result.saved_id).toBeUndefined(); + expect(result.type).toBe('query'); + expect(result.query).toBe('test query'); + }); + + test('returns saved_query rule if shouldLoadQueryDynamically = true and savedId provided for rule type query', () => { + const mockStepData: DefineStepRule = { + ...mockData, + queryBar: { + ...mockData.queryBar, + saved_id: 'mock-test-id', + }, + ruleType: 'query', + shouldLoadQueryDynamically: true, + }; + const result = formatDefineStepData(mockStepData); + + expect(result.saved_id).toBe('mock-test-id'); + expect(result.type).toBe('saved_query'); + expect(result.query).toBeUndefined(); + }); + + test('returns saved_query rule if shouldLoadQueryDynamically = true and savedId provided for rule type saved_query', () => { + const mockStepData: DefineStepRule = { + ...mockData, + queryBar: { + ...mockData.queryBar, + saved_id: 'mock-test-id', + }, + ruleType: 'saved_query', + shouldLoadQueryDynamically: true, + }; + const result = formatDefineStepData(mockStepData); + + expect(result.saved_id).toBe('mock-test-id'); + expect(result.type).toBe('saved_query'); + expect(result.query).toBeUndefined(); + }); }); - test('returns formatted object without timeline_id and timeline_title if timeline.id is null', () => { + test('returns undefined timeline_id and timeline_title if timeline.id is undefined', () => { const mockStepData: DefineStepRule = { ...mockData, }; @@ -160,19 +238,11 @@ describe('helpers', () => { const result = formatDefineStepData(mockStepData); - const expected: DefineStepRuleJson = { - language: 'kuery', - filters: mockQueryBar.filters, - query: 'test query', - index: ['filebeat-'], - saved_id: 'test123', - type: 'saved_query', - }; - - expect(result).toEqual(expected); + expect(result.timeline_id).toBeUndefined(); + expect(result.timeline_title).toBeUndefined(); }); - test('returns formatted object with timeline_id and timeline_title if timeline.id is "', () => { + test('returns formatted timeline_id and timeline_title if timeline.id is empty string', () => { const mockStepData: DefineStepRule = { ...mockData, timeline: { @@ -182,21 +252,11 @@ describe('helpers', () => { }; const result = formatDefineStepData(mockStepData); - const expected: DefineStepRuleJson = { - language: 'kuery', - filters: mockQueryBar.filters, - query: 'test query', - index: ['filebeat-'], - saved_id: 'test123', - type: 'saved_query', - timeline_id: '', - timeline_title: 'Titled timeline', - }; - - expect(result).toEqual(expected); + expect(result.timeline_id).toBe(''); + expect(result.timeline_title).toEqual('Titled timeline'); }); - test('returns formatted object without timeline_id and timeline_title if timeline.title is null', () => { + test('returns undefined timeline_id and timeline_title if timeline.title is undefined', () => { const mockStepData: DefineStepRule = { ...mockData, timeline: { @@ -208,19 +268,11 @@ describe('helpers', () => { delete mockStepData.timeline.title; const result = formatDefineStepData(mockStepData); - const expected: DefineStepRuleJson = { - language: 'kuery', - filters: mockQueryBar.filters, - query: 'test query', - index: ['filebeat-'], - saved_id: 'test123', - type: 'saved_query', - }; - - expect(result).toEqual(expected); + expect(result.timeline_id).toBeUndefined(); + expect(result.timeline_title).toBeUndefined(); }); - test('returns formatted object with timeline_id and timeline_title if timeline.title is "', () => { + test('returns formatted object with timeline_id and timeline_title if timeline.title is empty string', () => { const mockStepData: DefineStepRule = { ...mockData, timeline: { @@ -230,18 +282,8 @@ describe('helpers', () => { }; const result = formatDefineStepData(mockStepData); - const expected: DefineStepRuleJson = { - language: 'kuery', - filters: mockQueryBar.filters, - query: 'test query', - index: ['filebeat-'], - saved_id: 'test123', - type: 'saved_query', - timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', - timeline_title: '', - }; - - expect(result).toEqual(expected); + expect(result.timeline_id).toBe('86aa74d0-2136-11ea-9864-ebc8cc1cb8c2'); + expect(result.timeline_title).toEqual(''); }); test('returns ML fields if type is machine_learning', () => { @@ -891,9 +933,20 @@ describe('helpers', () => { mockActions = mockActionsStepRule(); }); - test('returns rule with type of saved_query when saved_id exists', () => { + test('returns rule with type of query when saved_id exists but shouldLoadQueryDynamically=false', () => { const result = formatRule(mockDefine, mockAbout, mockSchedule, mockActions); + expect(result.type).toEqual('query'); + }); + + test('returns rule with type of saved_query when saved_id exists and shouldLoadQueryDynamically=true', () => { + const result = formatRule( + { ...mockDefine, shouldLoadQueryDynamically: true }, + mockAbout, + mockSchedule, + mockActions + ); + expect(result.type).toEqual('saved_query'); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts index 59f2d0e6fd713..0406369c541ee 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts @@ -435,9 +435,17 @@ export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStep filters: ruleFields.queryBar?.filters, language: ruleFields.queryBar?.query?.language, query: ruleFields.queryBar?.query?.query as string, - saved_id: ruleFields.queryBar?.saved_id ?? undefined, - ...(ruleType === 'query' && - ruleFields.queryBar?.saved_id && { type: 'saved_query' as Type }), + saved_id: undefined, + type: 'query' as Type, + // rule only be updated as saved_query type if it has saved_id and shouldLoadQueryDynamically checkbox checked + ...(['query', 'saved_query'].includes(ruleType) && + ruleFields.queryBar?.saved_id && + ruleFields.shouldLoadQueryDynamically && { + type: 'saved_query' as Type, + query: undefined, + filters: undefined, + saved_id: ruleFields.queryBar.saved_id, + }), }; return { ...baseFields, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 7b3c884657707..e91756ce74db4 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -116,6 +116,7 @@ import { ExecutionLogTable } from './execution_log_table/execution_log_table'; import * as detectionI18n from '../../translations'; import * as ruleI18n from '../translations'; import { RuleDetailsContextProvider } from './rule_details_context'; +import { useGetSavedQuery } from '../../../../../common/hooks/use_get_saved_query'; import * as i18n from './translations'; import { NeedAdminForUpdateRulesCallOut } from '../../../../components/callouts/need_admin_for_update_callout'; import { MissingPrivilegesCallOut } from '../../../../components/callouts/missing_privileges_callout'; @@ -305,6 +306,10 @@ const RuleDetailsPageComponent: React.FC = ({ const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); const [dataViewOptions, setDataViewOptions] = useState<{ [x: string]: DataViewListItem }>({}); + // load saved query only if rule type === 'saved_query', as other rule types still can have saved_id property that is not used + const savedQueryId = rule?.type === 'saved_query' ? rule?.saved_id : undefined; + const { isSavedQueryLoading, savedQueryBar } = useGetSavedQuery(savedQueryId); + const [indicesConfig] = useUiSetting$(DEFAULT_INDEX_KEY); const [threatIndicesConfig] = useUiSetting$(DEFAULT_THREAT_INDEX_KEY); @@ -765,14 +770,21 @@ const RuleDetailsPageComponent: React.FC = ({ - - - {defineRuleData != null && ( + + + {defineRuleData != null && !isSavedQueryLoading && ( { dataViewId: undefined, index: ['auditbeat-*'], machineLearningJobId: [], + shouldLoadQueryDynamically: true, queryBar: { query: { query: 'user.name: root or user.name: admin', @@ -256,6 +257,7 @@ describe('rule helpers', () => { }, newTermsFields: [], historyWindowSize: '7d', + shouldLoadQueryDynamically: true, }; expect(result).toEqual(expected); @@ -309,6 +311,7 @@ describe('rule helpers', () => { }, newTermsFields: [], historyWindowSize: '7d', + shouldLoadQueryDynamically: false, }; expect(result).toEqual(expected); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index 278ed497d12e8..2479815ad8fbb 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -126,6 +126,7 @@ export const getDefineStepsData = (rule: Rule): DefineStepRule => ({ historyWindowSize: rule.history_window_start ? convertHistoryStartToSize(rule.history_window_start) : '7d', + shouldLoadQueryDynamically: Boolean(rule.type === 'saved_query' && rule.saved_id), }); const convertHistoryStartToSize = (relativeTime: string) => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index d262cd44dadfc..c35f5907901a7 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts @@ -162,6 +162,7 @@ export interface DefineStepRule { dataSourceType: DataSourceType; newTermsFields: string[]; historyWindowSize: string; + shouldLoadQueryDynamically: boolean; } export interface ScheduleStepRule { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts index 806c236fd63cc..26f23bba9591d 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts @@ -139,6 +139,7 @@ export const stepDefineDefaultValue: DefineStepRule = { dataSourceType: DataSourceType.IndexPatterns, newTermsFields: [], historyWindowSize: '7d', + shouldLoadQueryDynamically: false, }; export const stepAboutDefaultValue: AboutStepRule = { diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx index a5ebfadd071eb..1da0711f21aa7 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx @@ -24,7 +24,6 @@ import { HostsTableType } from '../../store/model'; import { HostsTable } from '.'; import { mockData } from './mock'; import { render } from '@testing-library/react'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; jest.mock('../../../common/lib/kibana'); @@ -45,7 +44,11 @@ jest.mock('../../../common/components/query_bar', () => ({ jest.mock('../../../common/components/link_to'); -jest.mock('../../../common/hooks/use_experimental_features'); +const mockUseMlCapabilities = jest.fn(); + +jest.mock('../../../common/components/ml/hooks/use_ml_capabilities', () => ({ + useMlCapabilities: () => mockUseMlCapabilities(), +})); describe('Hosts Table', () => { const loadPage = jest.fn(); @@ -81,8 +84,8 @@ describe('Hosts Table', () => { expect(wrapper.find('HostsTable')).toMatchSnapshot(); }); - test('it renders "Host Risk classfication" column when "riskyHostsEnabled" feature flag is enabled', () => { - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + test('it renders "Host Risk classfication" column when "isPlatinumOrTrialLicense" is truthy', () => { + mockUseMlCapabilities.mockReturnValue({ isPlatinumOrTrialLicense: true }); const { queryByTestId } = render( @@ -104,8 +107,8 @@ describe('Hosts Table', () => { expect(queryByTestId('tableHeaderCell_node.risk_4')).toBeInTheDocument(); }); - test("it doesn't renders 'Host Risk classfication' column when 'riskyHostsEnabled' feature flag is disabled", () => { - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false); + test("it doesn't renders 'Host Risk classfication' column when 'isPlatinumOrTrialLicense' is falsy", () => { + mockUseMlCapabilities.mockReturnValue({ isPlatinumOrTrialLicense: false }); const { queryByTestId } = render( diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.tsx index 6b701b7f7a8ef..4aeb80bbdeecd 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.tsx @@ -27,10 +27,10 @@ import type { import { HostsFields } from '../../../../common/search_strategy/security_solution/hosts'; import type { Direction, RiskSeverity } from '../../../../common/search_strategy'; import type { HostEcs, OsEcs } from '../../../../common/ecs/host'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { SecurityPageName } from '../../../../common/constants'; import { HostsTableType } from '../../store/model'; import { useNavigateTo } from '../../../common/lib/kibana/hooks'; +import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities'; const tableType = hostsModel.HostsTableType.hosts; @@ -132,7 +132,7 @@ const HostsTableComponent: React.FC = ({ }, [direction, sortField, type, dispatch] ); - const riskyHostsFeatureEnabled = useIsExperimentalFeatureEnabled('riskyHostsEnabled'); + const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; const dispatchSeverityUpdate = useCallback( (s: RiskSeverity) => { @@ -151,8 +151,8 @@ const HostsTableComponent: React.FC = ({ ); const hostsColumns = useMemo( - () => getHostsColumns(riskyHostsFeatureEnabled, dispatchSeverityUpdate), - [dispatchSeverityUpdate, riskyHostsFeatureEnabled] + () => getHostsColumns(isPlatinumOrTrialLicense, dispatchSeverityUpdate), + [dispatchSeverityUpdate, isPlatinumOrTrialLicense] ); const sorting = useMemo(() => getSorting(sortField, direction), [sortField, direction]); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx index f7c9352f3a951..ea7e12b75a5f8 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx @@ -18,11 +18,11 @@ import { RISKY_HOSTS_DOC_LINK } from '../../../../common/constants'; export const HostsKpiComponent = React.memo( ({ filterQuery, from, indexNames, to, setQuery, skip, updateDateRange }) => { - const [_, { isModuleEnabled }] = useHostRiskScore(); + const [loading, { isLicenseValid, isModuleEnabled }] = useHostRiskScore(); return ( <> - {isModuleEnabled === false && ( + {isLicenseValid && !isModuleEnabled && !loading && ( <> = ({ detailName, hostDeta dispatch(setHostDetailsTablesActivePageToZero()); }, [dispatch, detailName]); - const riskyHostsFeatureEnabled = useIsExperimentalFeatureEnabled('riskyHostsEnabled'); + const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; return ( <> @@ -206,7 +205,7 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.test.tsx index 442f16dc444fe..3dcebfc19706f 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.test.tsx @@ -52,21 +52,31 @@ describe('navTabsHostDetails', () => { expect(tabs).toHaveProperty(HostsTableType.risk); }); - test('it should display Beta badge for sessions tab only', () => { + test('it should display Beta badge for sessions tab', () => { const tabs = navTabsHostDetails({ hasMlUserPermissions: false, isRiskyHostsEnabled: true, hostName: mockHostName, }); - Object.values(tabs).forEach((item) => { - const tab = item as TabNavigationItemProps; + const sessionsTab = Object.values(tabs).find( + (item) => item.id === HostsTableType.sessions + ); - if (tab.id === HostsTableType.sessions) { - expect(tab.isBeta).toEqual(true); - } else { - expect(tab.isBeta).toEqual(undefined); - } + expect(sessionsTab?.isBeta).toEqual(true); + }); + + test('it should display Beta badge for risk tab', () => { + const tabs = navTabsHostDetails({ + hasMlUserPermissions: false, + isRiskyHostsEnabled: true, + hostName: mockHostName, }); + + const riskTab = Object.values(tabs).find( + (item) => item.id === HostsTableType.risk + ); + + expect(riskTab?.isBeta).toEqual(true); }); }); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx index d070b19035dfe..7113131e3c3fc 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx @@ -10,6 +10,7 @@ import * as i18n from '../translations'; import type { HostDetailsNavTab } from './types'; import { HostsTableType } from '../../store/model'; import { HOSTS_PATH } from '../../../../common/constants'; +import { TECHNICAL_PREVIEW } from '../../../overview/pages/translations'; const getTabsOnHostDetailsUrl = (hostName: string, tabName: HostsTableType) => `${HOSTS_PATH}/name/${hostName}/${tabName}`; @@ -55,6 +56,10 @@ export const navTabsHostDetails = ({ name: i18n.NAVIGATION_HOST_RISK_TITLE, href: getTabsOnHostDetailsUrl(hostName, HostsTableType.risk), disabled: false, + isBeta: true, + betaOptions: { + text: TECHNICAL_PREVIEW, + }, }, [HostsTableType.sessions]: { id: HostsTableType.sessions, diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index 9d6687a771f36..f853502c22666 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -55,8 +55,6 @@ import { useSourcererDataView } from '../../common/containers/sourcerer'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../common/hooks/use_selector'; import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query'; import { ID } from '../containers/hosts'; -import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; - import { LandingPageComponent } from '../../common/components/landing_page'; import { hostNameExistsFilter } from '../../common/components/visualization_actions/utils'; @@ -146,8 +144,6 @@ const HostsComponent = () => { [indexPattern, query, tabsFilters, uiSettings] ); - const riskyHostsFeatureEnabled = useIsExperimentalFeatureEnabled('riskyHostsEnabled'); - useInvalidFilterQuery({ id: ID, filterQuery, kqlError, query, startDate: from, endDate: to }); const onSkipFocusBeforeEventsTable = useCallback(() => { @@ -208,7 +204,7 @@ const HostsComponent = () => { diff --git a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx index e8ec705bd84d9..ba278ad7fdb59 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx @@ -10,6 +10,7 @@ import * as i18n from './translations'; import { HostsTableType } from '../store/model'; import type { HostsNavTab } from './navigation/types'; import { HOSTS_PATH } from '../../../common/constants'; +import { TECHNICAL_PREVIEW } from '../../overview/pages/translations'; const getTabsOnHostsUrl = (tabName: HostsTableType) => `${HOSTS_PATH}/${tabName}`; @@ -51,6 +52,10 @@ export const navTabsHosts = ({ name: i18n.NAVIGATION_HOST_RISK_TITLE, href: getTabsOnHostsUrl(HostsTableType.risk), disabled: false, + isBeta: true, + betaOptions: { + text: TECHNICAL_PREVIEW, + }, }, [HostsTableType.sessions]: { id: HostsTableType.sessions, diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/host_risk_score_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/host_risk_score_tab_body.tsx index c790b3c7df186..40480aafc1a68 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/host_risk_score_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/host_risk_score_tab_body.tsx @@ -7,6 +7,9 @@ import React, { useEffect, useMemo, useState } from 'react'; import { noop } from 'lodash/fp'; +import { useGlobalTime } from '../../../common/containers/use_global_time'; +import { RiskEntity } from '../../../risk_score/containers/feature_status/api'; +import { RiskScoresDeprecated } from '../../../common/components/risk_score_deprecated'; import type { HostsComponentsQueryProps } from './types'; import { manageQuery } from '../../../common/components/page/manage_query'; import { HostRiskScoreTable } from '../../components/host_risk_score_table'; @@ -31,9 +34,9 @@ export const HostRiskScoreQueryTabBody = ({ startDate, type, }: HostsComponentsQueryProps) => { - const getHosRiskScoreSelector = useMemo(() => hostsSelectors.hostRiskScoreSelector(), []); + const getHostRiskScoreSelector = useMemo(() => hostsSelectors.hostRiskScoreSelector(), []); const { activePage, limit, sort } = useDeepEqualSelector((state: State) => - getHosRiskScoreSelector(state, hostsModel.HostsType.page) + getHostRiskScoreSelector(state, hostsModel.HostsType.page) ); const pagination = useMemo( @@ -49,19 +52,25 @@ export const HostRiskScoreQueryTabBody = ({ useEffect(() => { setQuerySkip(!toggleStatus); }, [toggleStatus]); - - const [loading, { data, totalCount, inspect, isInspected, refetch }] = useHostRiskScore({ - filterQuery, - skip: querySkip, - pagination, - sort, - }); + const { from, to } = useGlobalTime(); + const [loading, { data, totalCount, inspect, isInspected, isDeprecated, refetch }] = + useHostRiskScore({ + filterQuery, + skip: querySkip, + pagination, + sort, + timerange: { from, to }, + }); const { severityCount, loading: isKpiLoading } = useHostRiskScoreKpi({ filterQuery, skip: querySkip, }); + if (isDeprecated) { + return ; + } + return ( ; + } + return ( <> diff --git a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.tsx b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.tsx index b82ecc9215a05..7ccd58117b3b2 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_icons.tsx @@ -36,7 +36,7 @@ const StyledEuiTitle = styled(EuiTitle)` export const LandingLinksIcons: React.FC = ({ items }) => ( - {items.map(({ title, description, id, icon, isBeta }) => ( + {items.map(({ title, description, id, icon, isBeta, betaOptions }) => ( = ({ items }) {isBeta && ( - + )} diff --git a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.tsx b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.tsx index b5479e0e6c5a9..ff7c791c01e32 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/components/landing_links_images.tsx @@ -57,7 +57,7 @@ const SecuritySolutionLink = withSecuritySolutionLink(Link); export const LandingLinksImages: React.FC = ({ items }) => ( - {items.map(({ title, description, image, id, isBeta }) => ( + {items.map(({ title, description, image, id, isBeta, betaOptions }) => ( {/* Empty onClick is to force hover style on `EuiPanel` */} @@ -78,7 +78,7 @@ export const LandingLinksImages: React.FC = ({ items }) => ( {title} - {isBeta && } + {isBeta && } @@ -114,7 +114,7 @@ const SecuritySolutionCard = withSecuritySolutionLink(PrimaryTitleCard); export const LandingImageCards: React.FC = React.memo(({ items }) => ( - {items.map(({ id, image, title, description, isBeta }) => ( + {items.map(({ id, image, title, description, isBeta, betaOptions }) => ( = React.memo(({ ite {title} - {isBeta && } + {isBeta && } } diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx index e26f1375984b5..e4f03aec7953a 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { memo, useMemo, useCallback } from 'react'; +import { orderBy } from 'lodash/fp'; +import React, { memo, useEffect, useMemo, useState, useCallback, useRef } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSelectable, EuiPopoverTitle } from '@elastic/eui'; import type { ResponseActions } from '../../../../../common/endpoint/service/response_actions/constants'; import { ActionsLogFilterPopover } from './actions_log_filter_popover'; @@ -25,19 +26,69 @@ export const ActionsLogFilter = memo( onChangeFilterOptions: (selectedOptions: string[]) => void; }) => { const getTestId = useTestIdGenerator('response-actions-list'); + + // popover states and handlers + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const onPopoverButtonClick = useCallback(() => { + setIsPopoverOpen(!isPopoverOpen); + }, [setIsPopoverOpen, isPopoverOpen]); + const onClosePopover = useCallback(() => { + setIsPopoverOpen(false); + }, [setIsPopoverOpen]); + + // search string state + const [searchString, setSearchString] = useState(''); const { + areHostsSelectedOnMount, + isLoading, items, setItems, hasActiveFilters, numActiveFilters, numFilters, + setAreHostsSelectedOnMount, setUrlActionsFilters, + setUrlHostsFilters, setUrlStatusesFilters, - } = useActionsLogFilter(filterName, isFlyout); + } = useActionsLogFilter({ + filterName, + isFlyout, + isPopoverOpen, + searchString, + }); + + // track popover state to pin selected options + const wasPopoverOpen = useRef(isPopoverOpen); + useEffect(() => { + return () => { + wasPopoverOpen.current = isPopoverOpen; + }; + }, [isPopoverOpen, wasPopoverOpen]); + + // compute if selected hosts should be pinned + const shouldPinSelectedHosts = useCallback( + (isNotChangingOptions: boolean = true) => { + // case 1: when no hosts are selected initially + return ( + isNotChangingOptions && wasPopoverOpen.current && isPopoverOpen && filterName === 'hosts' + ); + }, + [filterName, isPopoverOpen] + ); + + // augmented options based on hosts filter + const sortedHostsFilterOptions = useMemo(() => { + if (shouldPinSelectedHosts() || areHostsSelectedOnMount) { + // pin checked items to the top + return orderBy('checked', 'asc', items); + } + // return options as is for other filters + return items; + }, [areHostsSelectedOnMount, shouldPinSelectedHosts, items]); const isSearchable = useMemo(() => filterName !== 'statuses', [filterName]); - const onChange = useCallback( + const onOptionsChange = useCallback( (newOptions: FilterItems) => { // update filter UI options state setItems(newOptions.map((option) => option)); @@ -56,20 +107,28 @@ export const ActionsLogFilter = memo( setUrlActionsFilters( selectedItems.map((item) => getUiCommand(item as ResponseActions)).join() ); + } else if (filterName === 'hosts') { + setUrlHostsFilters(selectedItems.join()); } else if (filterName === 'statuses') { setUrlStatusesFilters(selectedItems.join()); } + // reset shouldPinSelectedHosts, setAreHostsSelectedOnMount + shouldPinSelectedHosts(false); + setAreHostsSelectedOnMount(false); } // update query state onChangeFilterOptions(selectedItems); }, [ + shouldPinSelectedHosts, filterName, isFlyout, setItems, onChangeFilterOptions, + setAreHostsSelectedOnMount, setUrlActionsFilters, + setUrlHostsFilters, setUrlStatusesFilters, ] ); @@ -85,9 +144,11 @@ export const ActionsLogFilter = memo( ); if (!isFlyout) { - // update URL params + // update URL params based on filter if (filterName === 'actions') { setUrlActionsFilters(''); + } else if (filterName === 'hosts') { + setUrlHostsFilters(''); } else if (filterName === 'statuses') { setUrlStatusesFilters(''); } @@ -101,24 +162,31 @@ export const ActionsLogFilter = memo( setItems, onChangeFilterOptions, setUrlActionsFilters, + setUrlHostsFilters, setUrlStatusesFilters, ]); return ( setSearchString(searchValue.trim()), }} > {(list, search) => { diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter_popover.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter_popover.tsx index 9d2aada871707..2ee4ce50ac033 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter_popover.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filter_popover.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useState, useCallback, useMemo } from 'react'; +import React, { memo, useMemo } from 'react'; import { EuiPopover, EuiFilterButton, useGeneratedHtmlId } from '@elastic/eui'; import { FILTER_NAMES } from '../translations'; import type { FilterName } from './hooks'; @@ -14,25 +14,25 @@ import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; export const ActionsLogFilterPopover = memo( ({ children, + closePopover, filterName, hasActiveFilters, + isPopoverOpen, numActiveFilters, numFilters, + onButtonClick, }: { children: React.ReactNode; + closePopover: () => void; filterName: FilterName; hasActiveFilters: boolean; + isPopoverOpen: boolean; numActiveFilters: number; numFilters: number; + onButtonClick: () => void; }) => { const getTestId = useTestIdGenerator('response-actions-list'); - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const onButtonClick = useCallback(() => { - setIsPopoverOpen(!isPopoverOpen); - }, [setIsPopoverOpen, isPopoverOpen]); - const closePopover = useCallback(() => { - setIsPopoverOpen(false); - }, [setIsPopoverOpen]); + const filterGroupPopoverId = useGeneratedHtmlId({ prefix: 'filterGroupPopover', }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx index 9b65a2b33f752..564a8f0d8e084 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx @@ -24,27 +24,37 @@ export const ActionsLogFilters = memo( isDataLoading, isFlyout, onClick, + onChangeHostsFilter, onChangeCommandsFilter, onChangeStatusesFilter, onRefresh, onRefreshChange, onTimeChange, + showHostsFilter, }: { dateRangePickerState: DateRangePickerValues; isDataLoading: boolean; isFlyout: boolean; + onChangeHostsFilter: (selectedCommands: string[]) => void; onChangeCommandsFilter: (selectedCommands: string[]) => void; onChangeStatusesFilter: (selectedStatuses: string[]) => void; onRefresh: () => void; onRefreshChange: (evt: OnRefreshChangeProps) => void; onTimeChange: ({ start, end }: DurationRange) => void; onClick: ReturnType['refetch']; + showHostsFilter: boolean; }) => { const getTestId = useTestIdGenerator('response-actions-list'); const filters = useMemo(() => { - // TODO: add more filter names here (users, hosts, statuses) return ( <> + {showHostsFilter && ( + + )} ); - }, [isFlyout, onChangeCommandsFilter, onChangeStatusesFilter]); + }, [ + isFlyout, + onChangeCommandsFilter, + onChangeHostsFilter, + onChangeStatusesFilter, + showHostsFilter, + ]); const onClickRefreshButton = useCallback(() => onClick(), [onClick]); 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 4552d3912f842..bb2c1b7761d5a 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState, useEffect } from 'react'; import type { DurationRange, OnRefreshChangeProps, @@ -23,6 +23,7 @@ import type { FILTER_NAMES } from '../translations'; import { UX_MESSAGES } from '../translations'; import { StatusBadge } from './status_badge'; import { useActionHistoryUrlParams } from './use_action_history_url_params'; +import { useGetEndpointsList } from '../../../hooks/endpoint/use_get_endpoints_list'; const defaultDateRangeOptions = Object.freeze({ autoRefreshOptions: { @@ -160,21 +161,56 @@ export const getCommandKey = ( // TODO: add more filter names here export type FilterName = keyof typeof FILTER_NAMES; -export const useActionsLogFilter = ( - filterName: FilterName, - isFlyout: boolean -): { +export const useActionsLogFilter = ({ + filterName, + isFlyout, + isPopoverOpen, + searchString, +}: { + filterName: FilterName; + isFlyout: boolean; + isPopoverOpen: boolean; + searchString: string; +}): { + areHostsSelectedOnMount: boolean; + isLoading: boolean; items: FilterItems; setItems: React.Dispatch>; hasActiveFilters: boolean; numActiveFilters: number; numFilters: number; + setAreHostsSelectedOnMount: (value: React.SetStateAction) => void; setUrlActionsFilters: ReturnType['setUrlActionsFilters']; + setUrlHostsFilters: ReturnType['setUrlHostsFilters']; setUrlStatusesFilters: ReturnType['setUrlStatusesFilters']; } => { - const { commands, statuses, setUrlActionsFilters, setUrlStatusesFilters } = - useActionHistoryUrlParams(); + const { + commands, + statuses, + hosts: selectedAgentIdsFromUrl, + setUrlActionsFilters, + setUrlHostsFilters, + setUrlStatusesFilters, + } = useActionHistoryUrlParams(); const isStatusesFilter = filterName === 'statuses'; + const isHostsFilter = filterName === 'hosts'; + const { data: endpointsList, isFetching } = useGetEndpointsList({ + searchString, + selectedAgentIds: selectedAgentIdsFromUrl, + }); + + // track state of selected hosts via URL + // when page is loaded via selected hosts on URL + const [areHostsSelectedOnMount, setAreHostsSelectedOnMount] = useState(false); + useEffect(() => { + if (selectedAgentIdsFromUrl && selectedAgentIdsFromUrl.length > 0) { + setAreHostsSelectedOnMount(true); + } + // don't sync with changes to further selectedAgentIdsFromUrl + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // filter options const [items, setItems] = useState( isStatusesFilter ? RESPONSE_ACTION_STATUS.map((statusName) => ({ @@ -194,6 +230,8 @@ export const useActionsLogFilter = ( checked: !isFlyout && statuses?.includes(statusName) ? 'on' : undefined, 'data-test-subj': `${filterName}-filter-option`, })) + : isHostsFilter + ? [] : RESPONSE_ACTION_COMMANDS.map((commandName) => ({ key: commandName, label: getUiCommand(commandName), @@ -205,6 +243,19 @@ export const useActionsLogFilter = ( })) ); + useEffect(() => { + if (isHostsFilter && endpointsList) { + setItems( + endpointsList?.map((list) => ({ + key: list.id, + label: list.name, + checked: !isFlyout && list.selected ? 'on' : undefined, + 'data-test-subj': `${filterName}-filter-option`, + })) + ); + } + }, [endpointsList, filterName, isFlyout, isHostsFilter, setItems]); + const hasActiveFilters = useMemo(() => !!items.find((item) => item.checked === 'on'), [items]); const numActiveFilters = useMemo( () => items.filter((item) => item.checked === 'on').length, @@ -213,12 +264,16 @@ export const useActionsLogFilter = ( const numFilters = useMemo(() => items.filter((item) => item.checked !== 'on').length, [items]); return { + areHostsSelectedOnMount, + isLoading: isHostsFilter && isFetching, items, setItems, hasActiveFilters, numActiveFilters, numFilters, + setAreHostsSelectedOnMount, setUrlActionsFilters, + setUrlHostsFilters, setUrlStatusesFilters, }; }; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx index 79c99d62e2ff6..03ef9172fd469 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.tsx @@ -117,6 +117,7 @@ export const ResponseActionsLog = memo< useUrlPagination(); const { commands: commandsFromUrl, + hosts: agentIdsFromUrl, statuses: statusesFromUrl, startDate: startDateFromUrl, endDate: endDateFromUrl, @@ -130,7 +131,7 @@ export const ResponseActionsLog = memo< const [queryParams, setQueryParams] = useState({ page: isFlyout ? 1 : paginationFromUrlParams.page, pageSize: isFlyout ? 10 : paginationFromUrlParams.pageSize, - agentIds, + agentIds: isFlyout ? agentIds : agentIdsFromUrl?.length ? agentIdsFromUrl : agentIds, commands: [], statuses: [], userIds: [], @@ -144,12 +145,13 @@ export const ResponseActionsLog = memo< commands: commandsFromUrl?.length ? commandsFromUrl.map((commandFromUrl) => getCommandKey(commandFromUrl)) : prevState.commands, + hosts: agentIdsFromUrl?.length ? agentIdsFromUrl : prevState.agentIds, statuses: statusesFromUrl?.length ? (statusesFromUrl as ResponseActionStatus[]) : prevState.statuses, })); } - }, [commandsFromUrl, isFlyout, statusesFromUrl, setQueryParams]); + }, [commandsFromUrl, agentIdsFromUrl, isFlyout, statusesFromUrl, setQueryParams]); // date range picker state and handlers const { dateRangePickerState, onRefreshChange, onTimeChange } = useDateRangePicker(isFlyout); @@ -196,6 +198,13 @@ export const ResponseActionsLog = memo< [setQueryParams] ); + // handle on change hosts filter + const onChangeHostsFilter = useCallback( + (selectedAgentIds: string[]) => { + setQueryParams((prevState) => ({ ...prevState, agentIds: selectedAgentIds })); + }, + [setQueryParams] + ); // total actions const totalItemCount = useMemo(() => actionList?.total ?? 0, [actionList]); @@ -573,11 +582,13 @@ export const ResponseActionsLog = memo< dateRangePickerState={dateRangePickerState} isDataLoading={isFetching} onClick={reFetchEndpointActionList} + onChangeHostsFilter={onChangeHostsFilter} onChangeCommandsFilter={onChangeCommandsFilter} onChangeStatusesFilter={onChangeStatusesFilter} onRefresh={onRefresh} onRefreshChange={onRefreshChange} onTimeChange={onTimeChange} + showHostsFilter={showHostNames} /> {isFetched && !totalItemCount ? ( diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx index f16feaeb94455..9efc4356c21a2 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx @@ -112,6 +112,11 @@ export const UX_MESSAGES = Object.freeze({ defaultMessage: 'Search {filterName}', values: { filterName }, }), + filterEmptyMessage: (filterName: string) => + i18n.translate('xpack.securitySolution.responseActionsList.list.filter.emptyMessage', { + defaultMessage: 'No {filterName} available', + values: { filterName }, + }), badge: { successful: i18n.translate( 'xpack.securitySolution.responseActionsList.list.item.badge.successful', @@ -160,6 +165,9 @@ export const FILTER_NAMES = Object.freeze({ actions: i18n.translate('xpack.securitySolution.responseActionsList.list.filter.actions', { defaultMessage: 'Actions', }), + hosts: i18n.translate('xpack.securitySolution.responseActionsList.list.filter.Hosts', { + defaultMessage: 'Hosts', + }), statuses: i18n.translate('xpack.securitySolution.responseActionsList.list.filter.statuses', { defaultMessage: 'Statuses', }), diff --git a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.ts b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.ts new file mode 100644 index 0000000000000..0804bef55b3e7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.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 type { AppContextTestRender, ReactQueryHookRenderer } from '../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; +import { useGetEndpointsList } from './use_get_endpoints_list'; +import { HOST_METADATA_LIST_ROUTE } from '../../../../common/endpoint/constants'; +import { useQuery as _useQuery } from '@tanstack/react-query'; +import { endpointMetadataHttpMocks } from '../../pages/endpoint_hosts/mocks'; + +const useQueryMock = _useQuery as jest.Mock; + +jest.mock('@tanstack/react-query', () => { + const actualReactQueryModule = jest.requireActual('@tanstack/react-query'); + + return { + ...actualReactQueryModule, + useQuery: jest.fn((...args) => actualReactQueryModule.useQuery(...args)), + }; +}); + +describe('useGetEndpointsList hook', () => { + let renderReactQueryHook: ReactQueryHookRenderer< + Parameters, + ReturnType + >; + let http: AppContextTestRender['coreStart']['http']; + let apiMocks: ReturnType; + + beforeEach(() => { + const testContext = createAppRootMockRenderer(); + + renderReactQueryHook = testContext.renderReactQueryHook as typeof renderReactQueryHook; + http = testContext.coreStart.http; + + apiMocks = endpointMetadataHttpMocks(http); + }); + + it('should call the API with kuery set to look for all hostnames when no search string given', async () => { + await renderReactQueryHook(() => useGetEndpointsList({ searchString: '' })); + + expect(apiMocks.responseProvider.metadataList).toHaveBeenCalledWith({ + path: HOST_METADATA_LIST_ROUTE, + query: { page: 0, pageSize: 50, kuery: 'united.endpoint.host.hostname:*' }, + }); + }); + + it('should call the API with kuery set to look for all matching hostnames', async () => { + await renderReactQueryHook(() => useGetEndpointsList({ searchString: 'xyz' })); + + expect(apiMocks.responseProvider.metadataList).toHaveBeenCalledWith({ + path: HOST_METADATA_LIST_ROUTE, + query: { page: 0, pageSize: 50, kuery: 'united.endpoint.host.hostname:*xyz*' }, + }); + }); + + it('should call the API with kuery set to look for all matching agentIds if present', async () => { + await renderReactQueryHook(() => + useGetEndpointsList({ searchString: '', selectedAgentIds: ['agent-a', 'agent-b'] }) + ); + + expect(apiMocks.responseProvider.metadataList).toHaveBeenCalledWith({ + path: HOST_METADATA_LIST_ROUTE, + query: { + page: 0, + pageSize: 10000, + kuery: + 'united.endpoint.agent.id:"agent-a" or united.endpoint.agent.id:"agent-b" or united.endpoint.host.hostname:*', + }, + }); + }); + + it('should call the API with kuery set to look for all matching agentIds and hostnames', async () => { + await renderReactQueryHook(() => + useGetEndpointsList({ searchString: 'xyz', selectedAgentIds: ['agent-a', 'agent-b'] }) + ); + + expect(apiMocks.responseProvider.metadataList).toHaveBeenCalledWith({ + path: HOST_METADATA_LIST_ROUTE, + query: { + page: 0, + pageSize: 10000, + kuery: + 'united.endpoint.agent.id:"agent-a" or united.endpoint.agent.id:"agent-b" or united.endpoint.host.hostname:*xyz*', + }, + }); + }); + + it('should allow custom options to be used', async () => { + await renderReactQueryHook( + () => + useGetEndpointsList({ + searchString: 'pqr', + options: { queryKey: ['m', 'n'], enabled: false }, + }), + false + ); + + expect(useQueryMock).toHaveBeenCalledWith( + expect.objectContaining({ + queryKey: ['m', 'n'], + enabled: false, + }) + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.ts b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.ts new file mode 100644 index 0000000000000..c0630ff7e04c3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { UseQueryOptions, UseQueryResult } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; +import { useHttp } from '../../../common/lib/kibana'; +import type { HostInfo, MetadataListResponse } from '../../../../common/endpoint/types'; +import { HOST_METADATA_LIST_ROUTE } from '../../../../common/endpoint/constants'; + +type GetEndpointsListResponse = Array<{ + id: HostInfo['metadata']['agent']['id']; + name: HostInfo['metadata']['host']['hostname']; + selected: boolean; +}>; + +const PAGING_PARAMS = Object.freeze({ + default: 50, + all: 10000, +}); +/** + * Get info for a security solution endpoint host using hostnames via kuery param + * page and pageSize are fixed for showing 50 hosts at most on the 1st page + * @param query + * @param options + */ +export const useGetEndpointsList = ({ + searchString, + selectedAgentIds, + options = {}, +}: { + searchString: string; + selectedAgentIds?: string[]; + options?: UseQueryOptions; +}): UseQueryResult => { + const http = useHttp(); + const kuery = `united.endpoint.host.hostname:${searchString.length ? `*${searchString}` : ''}*`; + let agentIdsKuery: string[] = []; + if (selectedAgentIds) { + agentIdsKuery = selectedAgentIds.map((id) => `united.endpoint.agent.id:"${id}"`); + } + + return useQuery({ + queryKey: ['get-endpoints-list', kuery], + ...options, + queryFn: async () => { + const metadataListResponse = await http.get(HOST_METADATA_LIST_ROUTE, { + query: { + page: 0, + pageSize: + // if the user has selected agents then search the whole index. + // as selected host could be somewhere after the 50 that are shown + // otherwise, limit the search to 50 hosts + selectedAgentIds && selectedAgentIds.length > 0 + ? PAGING_PARAMS.all + : PAGING_PARAMS.default, + kuery: [...agentIdsKuery, kuery].join(' or '), + }, + }); + + // pick out the selected agents and push them to the top of the list + const augmentedDataBasedOnSelectedAgents = metadataListResponse.data.reduce<{ + selected: GetEndpointsListResponse; + rest: GetEndpointsListResponse; + }>( + (acc, list) => { + const item = { + id: list.metadata.agent.id, + name: list.metadata.host.hostname, + }; + if (selectedAgentIds?.includes(list.metadata.agent.id)) { + acc.selected.push({ + ...item, + selected: true, + }); + } else { + acc.rest.push({ + ...item, + selected: false, + }); + } + return acc; + }, + { selected: [], rest: [] } + ); + + let selectedAgentIdsCount = 0; + if (selectedAgentIds) { + selectedAgentIdsCount = selectedAgentIds.length; + } + + // return 50 items max including the selected items + // unless all 50 items are selected then increase the list length by 10 + return [ + ...augmentedDataBasedOnSelectedAgents.selected, + ...augmentedDataBasedOnSelectedAgents.rest, + ].slice( + 0, + selectedAgentIdsCount >= PAGING_PARAMS.default + ? selectedAgentIdsCount + 10 + : PAGING_PARAMS.default + ); + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx index 8fb3f683e6eb5..2f0fc55979090 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx @@ -22,7 +22,8 @@ import { getFirstCard } from '../../../components/artifact_list_page/mocks'; jest.mock('../../../../common/components/user_privileges'); const useUserPrivilegesMock = _useUserPrivileges as jest.Mock; -describe('When on the host isolation exceptions page', () => { +// FLAKY: https://github.com/elastic/kibana/issues/140888 +describe.skip('When on the host isolation exceptions page', () => { let render: () => ReturnType; let renderResult: ReturnType; let history: AppContextTestRender['history']; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.test.tsx index feed7baf8819a..c7e7eb411d4f8 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.test.tsx @@ -24,8 +24,8 @@ const mockSeverityCount: SeverityCount = { [RiskSeverity.critical]: 99, }; -jest.mock('../../../../common/hooks/use_experimental_features', () => ({ - useIsExperimentalFeatureEnabled: () => true, +jest.mock('../../../../common/components/ml/hooks/use_ml_capabilities', () => ({ + useMlCapabilities: () => ({ isPlatinumOrTrialLicense: true, capabilities: {} }), })); jest.mock('../../../../risk_score/containers', () => { diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx index 4ee2bab00f1db..f94c7d52f6fce 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx @@ -9,6 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; import styled from 'styled-components'; import { useDispatch } from 'react-redux'; import { sum } from 'lodash/fp'; +import { ML_PAGES, useMlHref } from '@kbn/ml-plugin/public'; import { useHostRiskScoreKpi, useUserRiskScoreKpi } from '../../../../risk_score/containers'; import { LinkAnchor, useGetSecuritySolutionLinkProps } from '../../../../common/components/links'; import { Direction, RiskScoreFields, RiskSeverity } from '../../../../../common/search_strategy'; @@ -20,9 +21,10 @@ import { hostsActions } from '../../../../hosts/store'; import { usersActions } from '../../../../users/store'; import { getTabsOnUsersUrl } from '../../../../common/components/link_to/redirect_to_users'; import { UsersTableType } from '../../../../users/store/model'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { useNotableAnomaliesSearch } from '../../../../common/components/ml/anomaly/use_anomalies_search'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; +import { useKibana } from '../../../../common/lib/kibana'; +import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities'; const StyledEuiTitle = styled(EuiTitle)` color: ${({ theme: { eui } }) => eui.euiColorVis9}; @@ -35,8 +37,11 @@ export const EntityAnalyticsHeader = () => { const { data } = useNotableAnomaliesSearch({ skip: false, from, to }); const dispatch = useDispatch(); const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); - const riskyUsersFeatureEnabled = useIsExperimentalFeatureEnabled('riskyUsersEnabled'); - const riskyHostsFeatureEnabled = useIsExperimentalFeatureEnabled('riskyHostsEnabled'); + const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; + + const { + services: { ml, http }, + } = useKibana(); const [goToHostRiskTabFilterdByCritical, hostRiskTabUrl] = useMemo(() => { const { onClick, href } = getSecuritySolutionLinkProps({ @@ -85,13 +90,17 @@ export const EntityAnalyticsHeader = () => { const totalAnomalies = useMemo(() => sum(data.map(({ count }) => count)), [data]); + const jobsUrl = useMlHref(ml, http.basePath.get(), { + page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE, + }); + return ( - {riskyHostsFeatureEnabled && ( + {isPlatinumOrTrialLicense && ( - + {hostsSeverityCount[RiskSeverity.critical]} @@ -108,10 +117,10 @@ export const EntityAnalyticsHeader = () => { )} - {riskyUsersFeatureEnabled && ( + {isPlatinumOrTrialLicense && ( - + {usersSeverityCount[RiskSeverity.critical]} @@ -131,12 +140,16 @@ export const EntityAnalyticsHeader = () => { - + {totalAnomalies} - {i18n.ANOMALIES} + + + {i18n.ANOMALIES} + + diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.test.tsx index 156527bd11e11..4f4edd279a568 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.test.tsx @@ -11,6 +11,7 @@ import { TestProviders } from '../../../../common/mock'; import { EntityAnalyticsHostRiskScores } from '.'; import { RiskSeverity } from '../../../../../common/search_strategy'; import type { SeverityCount } from '../../../../common/components/severity/types'; +import { useHostRiskScore, useHostRiskScoreKpi } from '../../../../risk_score/containers'; const mockSeverityCount: SeverityCount = { [RiskSeverity.low]: 1, @@ -20,10 +21,6 @@ const mockSeverityCount: SeverityCount = { [RiskSeverity.critical]: 1, }; -jest.mock('../../../../common/hooks/use_experimental_features', () => ({ - useIsExperimentalFeatureEnabled: () => true, -})); - const mockUseQueryToggle = jest .fn() .mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() }); @@ -32,30 +29,26 @@ jest.mock('../../../../common/containers/query_toggle', () => { useQueryToggle: () => mockUseQueryToggle(), }; }); - -const mockUseHostRiskScore = jest - .fn() - .mockReturnValue([ - false, - { data: undefined, inspect: null, refetch: () => {}, isModuleEnabled: true }, - ]); -jest.mock('../../../../risk_score/containers', () => { - return { - useHostRiskScoreKpi: () => ({ severityCount: mockSeverityCount, loading: false }), - useHostRiskScore: (params: unknown) => mockUseHostRiskScore(params), - }; -}); +const defaultProps = { + data: undefined, + inspect: null, + refetch: () => {}, + isModuleEnabled: true, + isLicenseValid: true, +}; +const mockUseHostRiskScore = useHostRiskScore as jest.Mock; +const mockUseHostRiskScoreKpi = useHostRiskScoreKpi as jest.Mock; +jest.mock('../../../../risk_score/containers'); describe('EntityAnalyticsHostRiskScores', () => { beforeEach(() => { jest.clearAllMocks(); + mockUseHostRiskScoreKpi.mockReturnValue({ severityCount: mockSeverityCount, loading: false }); + mockUseHostRiskScore.mockReturnValue([false, defaultProps]); }); it('renders enable button when module is disable', () => { - mockUseHostRiskScore.mockReturnValue([ - false, - { data: undefined, inspect: null, refetch: () => {}, isModuleEnabled: false }, - ]); + mockUseHostRiskScore.mockReturnValue([false, { ...defaultProps, isModuleEnabled: false }]); const { getByTestId } = render( @@ -66,10 +59,6 @@ describe('EntityAnalyticsHostRiskScores', () => { }); it("doesn't render enable button when module is enable", () => { - mockUseHostRiskScore.mockReturnValue([ - false, - { data: undefined, inspect: null, refetch: () => {}, isModuleEnabled: true }, - ]); const { queryByTestId } = render( diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx index fa3cda0921c83..0825c7542ab67 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/host_risk_score/index.tsx @@ -17,6 +17,8 @@ import { import { useDispatch } from 'react-redux'; import styled from 'styled-components'; +import { RiskEntity } from '../../../../risk_score/containers/feature_status/api'; +import { RiskScoresDeprecated } from '../../../../common/components/risk_score_deprecated'; import { SeverityFilterGroup } from '../../../../common/components/severity/severity_filter_group'; import { LinkButton, useGetSecuritySolutionLinkProps } from '../../../../common/components/links'; import { getTabsOnHostsUrl } from '../../../../common/components/link_to/redirect_to_hosts'; @@ -40,7 +42,6 @@ import { useCheckSignalIndex } from '../../../../detections/containers/detection import { RiskScoreDonutChart } from '../common/risk_score_donut_chart'; import { BasicTableWithoutBorderBottom } from '../common/basic_table_without_border_bottom'; import { useEnableHostRiskFromUrl } from '../../../../common/hooks/use_enable_host_risk_from_url'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; const TABLE_QUERY_ID = 'hostRiskDashboardTable'; @@ -49,14 +50,13 @@ const IconWrapper = styled.span` `; export const EntityAnalyticsHostRiskScores = () => { - const { deleteQuery, setQuery } = useGlobalTime(); + const { deleteQuery, setQuery, to, from } = useGlobalTime(); const [updatedAt, setUpdatedAt] = useState(Date.now()); const { toggleStatus, setToggleStatus } = useQueryToggle(TABLE_QUERY_ID); const columns = useMemo(() => getHostRiskScoreColumns(), []); const [selectedSeverity, setSelectedSeverity] = useState([]); const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); const dispatch = useDispatch(); - const riskyHostsFeatureEnabled = useIsExperimentalFeatureEnabled('riskyHostsEnabled'); const severityFilter = useMemo(() => { const [filter] = generateSeverityFilter(selectedSeverity, RiskScoreEntity.host); @@ -69,13 +69,17 @@ export const EntityAnalyticsHostRiskScores = () => { skip: !toggleStatus, }); - const [isTableLoading, { data, inspect, refetch, isModuleEnabled }] = useHostRiskScore({ + const [ + isTableLoading, + { data, inspect, refetch, isDeprecated, isLicenseValid, isModuleEnabled }, + ] = useHostRiskScore({ filterQuery: severityFilter, skip: !toggleStatus, pagination: { cursorStart: 0, querySize: 5, }, + timerange: { to, from }, }); useQueryInspector({ @@ -124,7 +128,7 @@ export const EntityAnalyticsHostRiskScores = () => { ); }, []); - if (!riskyHostsFeatureEnabled) { + if (!isLicenseValid) { return null; } @@ -132,6 +136,10 @@ export const EntityAnalyticsHostRiskScores = () => { return ; } + if (isDeprecated) { + return ; + } + return ( diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.test.tsx index 030dbe51d74fc..6ddfd912e832f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.test.tsx @@ -11,6 +11,7 @@ import { TestProviders } from '../../../../common/mock'; import { EntityAnalyticsUserRiskScores } from '.'; import { RiskSeverity } from '../../../../../common/search_strategy'; import type { SeverityCount } from '../../../../common/components/severity/types'; +import { useUserRiskScore, useUserRiskScoreKpi } from '../../../../risk_score/containers'; const mockSeverityCount: SeverityCount = { [RiskSeverity.low]: 1, @@ -20,10 +21,6 @@ const mockSeverityCount: SeverityCount = { [RiskSeverity.critical]: 1, }; -jest.mock('../../../../common/hooks/use_experimental_features', () => ({ - useIsExperimentalFeatureEnabled: () => true, -})); - const mockUseQueryToggle = jest .fn() .mockReturnValue({ toggleStatus: false, setToggleStatus: jest.fn() }); @@ -33,29 +30,27 @@ jest.mock('../../../../common/containers/query_toggle', () => { }; }); -const mockUseUserRiskScore = jest - .fn() - .mockReturnValue([ - false, - { data: undefined, inspect: null, refetch: () => {}, isModuleEnabled: true }, - ]); -jest.mock('../../../../risk_score/containers', () => { - return { - useUserRiskScoreKpi: () => ({ severityCount: mockSeverityCount, loading: false }), - useUserRiskScore: (params: unknown) => mockUseUserRiskScore(params), - }; -}); +const defaultProps = { + data: undefined, + inspect: null, + refetch: () => {}, + isModuleEnabled: true, + isLicenseValid: true, +}; + +const mockUseUserRiskScore = useUserRiskScore as jest.Mock; +const mockUseUserRiskScoreKpi = useUserRiskScoreKpi as jest.Mock; +jest.mock('../../../../risk_score/containers'); describe('EntityAnalyticsUserRiskScores', () => { beforeEach(() => { jest.clearAllMocks(); + mockUseUserRiskScoreKpi.mockReturnValue({ severityCount: mockSeverityCount, loading: false }); + mockUseUserRiskScore.mockReturnValue([false, defaultProps]); }); it('renders enable button when module is disable', () => { - mockUseUserRiskScore.mockReturnValue([ - false, - { data: undefined, inspect: null, refetch: () => {}, isModuleEnabled: false }, - ]); + mockUseUserRiskScore.mockReturnValue([false, { ...defaultProps, isModuleEnabled: false }]); const { getByTestId } = render( @@ -66,10 +61,6 @@ describe('EntityAnalyticsUserRiskScores', () => { }); it("doesn't render enable button when module is enable", () => { - mockUseUserRiskScore.mockReturnValue([ - false, - { data: undefined, inspect: null, refetch: () => {}, isModuleEnabled: true }, - ]); const { queryByTestId } = render( diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.tsx index 68ed1082f4c05..097bba39efbfb 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/user_risk_score/index.tsx @@ -15,6 +15,8 @@ import { } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import styled from 'styled-components'; +import { RiskEntity } from '../../../../risk_score/containers/feature_status/api'; +import { RiskScoresDeprecated } from '../../../../common/components/risk_score_deprecated'; import { SeverityFilterGroup } from '../../../../common/components/severity/severity_filter_group'; import { LinkButton, useGetSecuritySolutionLinkProps } from '../../../../common/components/links'; import { LastUpdatedAt } from '../../detection_response/utils'; @@ -37,7 +39,6 @@ import { getTabsOnUsersUrl } from '../../../../common/components/link_to/redirec import { RISKY_USERS_DOC_LINK } from '../../../../users/components/constants'; import { RiskScoreDonutChart } from '../common/risk_score_donut_chart'; import { BasicTableWithoutBorderBottom } from '../common/basic_table_without_border_bottom'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; const TABLE_QUERY_ID = 'userRiskDashboardTable'; @@ -46,14 +47,13 @@ const IconWrapper = styled.span` `; export const EntityAnalyticsUserRiskScores = () => { - const { deleteQuery, setQuery } = useGlobalTime(); + const { deleteQuery, setQuery, from, to } = useGlobalTime(); const [updatedAt, setUpdatedAt] = useState(Date.now()); const { toggleStatus, setToggleStatus } = useQueryToggle(TABLE_QUERY_ID); const columns = useMemo(() => getUserRiskScoreColumns(), []); const [selectedSeverity, setSelectedSeverity] = useState([]); const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); const dispatch = useDispatch(); - const riskyUsersFeatureEnabled = useIsExperimentalFeatureEnabled('riskyUsersEnabled'); const severityFilter = useMemo(() => { const [filter] = generateSeverityFilter(selectedSeverity, RiskScoreEntity.user); @@ -66,13 +66,17 @@ export const EntityAnalyticsUserRiskScores = () => { skip: !toggleStatus, }); - const [isTableLoading, { data, inspect, refetch, isModuleEnabled }] = useUserRiskScore({ + const [ + isTableLoading, + { data, inspect, refetch, isLicenseValid, isDeprecated, isModuleEnabled }, + ] = useUserRiskScore({ filterQuery: severityFilter, skip: !toggleStatus, pagination: { cursorStart: 0, querySize: 5, }, + timerange: { to, from }, }); useQueryInspector({ @@ -120,7 +124,7 @@ export const EntityAnalyticsUserRiskScores = () => { ); }, []); - if (!riskyUsersFeatureEnabled) { + if (!isLicenseValid) { return null; } @@ -128,6 +132,10 @@ export const EntityAnalyticsUserRiskScores = () => { return ; } + if (isDeprecated) { + return ; + } + return ( diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/overview/components/host_overview/__snapshots__/index.test.tsx.snap index 540a36debf280..a79c7abde9cc8 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/__snapshots__/index.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Host Summary Component rendering it renders the default Host Summary 1`] = ` +exports[`Host Summary Component it renders the default Host Summary 1`] = ` `; -exports[`Host Summary Component rendering it renders the panel view Host Summary 1`] = ` +exports[`Host Summary Component it renders the panel view Host Summary 1`] = ` ({ - useHostRiskScore: jest.fn().mockReturnValue([ - true, - { - data: undefined, - isModuleEnabled: false, - }, - ]), -})); +const defaultProps = { + data: undefined, + inspect: null, + refetch: () => {}, + isModuleEnabled: true, + isLicenseValid: true, +}; + +jest.mock('../../../risk_score/containers/all'); + +const mockUseHostRiskScore = useHostRiskScore as jest.Mock; describe('Host Summary Component', () => { - describe('rendering', () => { - const mockProps = { - anomaliesData: mockAnomalies, - data: mockData.Hosts.edges[0].node, - endDate: '2019-06-18T06:00:00.000Z', - id: 'hostOverview', - indexNames: [], - isInDetailsSidePanel: false, - isLoadingAnomaliesData: false, - loading: false, - narrowDateRange: jest.fn(), - startDate: '2019-06-15T06:00:00.000Z', - hostName: 'testHostName', - }; + const mockProps = { + anomaliesData: mockAnomalies, + data: mockData.Hosts.edges[0].node, + endDate: '2019-06-18T06:00:00.000Z', + id: 'hostOverview', + indexNames: [], + isInDetailsSidePanel: false, + isLoadingAnomaliesData: false, + loading: false, + narrowDateRange: jest.fn(), + startDate: '2019-06-15T06:00:00.000Z', + hostName: 'testHostName', + }; - test('it renders the default Host Summary', () => { - const wrapper = shallow( - - - - ); + beforeEach(() => { + jest.clearAllMocks(); + mockUseHostRiskScore.mockReturnValue([true, { ...defaultProps, isModuleEnabled: false }]); + }); - expect(wrapper.find('HostOverview')).toMatchSnapshot(); - }); + test('it renders the default Host Summary', () => { + const wrapper = shallow( + + + + ); - test('it renders the panel view Host Summary', () => { - const panelViewProps = { - ...mockProps, - isInDetailsSidePanel: true, - }; + expect(wrapper.find('HostOverview')).toMatchSnapshot(); + }); - const wrapper = shallow( - - - - ); + test('it renders the panel view Host Summary', () => { + const panelViewProps = { + ...mockProps, + isInDetailsSidePanel: true, + }; - expect(wrapper.find('HostOverview')).toMatchSnapshot(); - }); + const wrapper = shallow( + + + + ); - test('it renders host risk score and level', () => { - const panelViewProps = { - ...mockProps, - isInDetailsSidePanel: true, - }; - const risk = 'very high host risk'; - const riskScore = 9999999; + expect(wrapper.find('HostOverview')).toMatchSnapshot(); + }); - (useHostRiskScore as jest.Mock).mockReturnValue([ - false, - { - data: [ - { - host: { - name: 'testHostmame', - risk: { - rule_risks: [], - calculated_score_norm: riskScore, - calculated_level: risk, - }, + test('it renders host risk score and level', () => { + const panelViewProps = { + ...mockProps, + isInDetailsSidePanel: true, + }; + const risk = 'very high host risk'; + const riskScore = 9999999; + mockUseHostRiskScore.mockReturnValue([ + false, + { + ...defaultProps, + data: [ + { + host: { + name: 'testHostmame', + risk: { + rule_risks: [], + calculated_score_norm: riskScore, + calculated_level: risk, }, }, - ], - isModuleEnabled: true, - }, - ]); + }, + ], + }, + ]); - const { getByTestId } = render( - - - - ); + const { getByTestId } = render( + + + + ); - expect(getByTestId('host-risk-overview')).toHaveTextContent(risk); - expect(getByTestId('host-risk-overview')).toHaveTextContent(riskScore.toString()); - }); + expect(getByTestId('host-risk-overview')).toHaveTextContent(risk); + expect(getByTestId('host-risk-overview')).toHaveTextContent(riskScore.toString()); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx index d3a1f601445fd..327d581b6d24a 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx @@ -10,6 +10,7 @@ import { euiLightVars as lightTheme, euiDarkVars as darkTheme } from '@kbn/ui-th import { getOr } from 'lodash/fp'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; +import { useGlobalTime } from '../../../common/containers/use_global_time'; import type { HostItem } from '../../../../common/search_strategy'; import { buildHostNamesFilter } from '../../../../common/search_strategy'; import { DEFAULT_DARK_MODE } from '../../../../common/constants'; @@ -82,10 +83,12 @@ export const HostOverview = React.memo( () => (hostName ? buildHostNamesFilter([hostName]) : undefined), [hostName] ); + const { from, to } = useGlobalTime(); - const [_, { data: hostRisk, isModuleEnabled }] = useHostRiskScore({ + const [_, { data: hostRisk, isLicenseValid }] = useHostRiskScore({ filterQuery, skip: hostName == null, + timerange: { to, from }, }); const getDefaultRenderer = useCallback( @@ -101,39 +104,32 @@ export const HostOverview = React.memo( ); const [hostRiskScore, hostRiskLevel] = useMemo(() => { - if (isModuleEnabled) { - const hostRiskData = hostRisk && hostRisk.length > 0 ? hostRisk[0] : undefined; - return [ - { - title: i18n.HOST_RISK_SCORE, - description: ( - <> - {hostRiskData - ? Math.round(hostRiskData.host.risk.calculated_score_norm) - : getEmptyTagValue()} - - ), - }, - - { - title: i18n.HOST_RISK_CLASSIFICATION, - description: ( - <> - {hostRiskData ? ( - - ) : ( - getEmptyTagValue() - )} - - ), - }, - ]; - } - return [undefined, undefined]; - }, [hostRisk, isModuleEnabled]); + const hostRiskData = hostRisk && hostRisk.length > 0 ? hostRisk[0] : undefined; + return [ + { + title: i18n.HOST_RISK_SCORE, + description: ( + <> + {hostRiskData + ? Math.round(hostRiskData.host.risk.calculated_score_norm) + : getEmptyTagValue()} + + ), + }, + { + title: i18n.HOST_RISK_CLASSIFICATION, + description: ( + <> + {hostRiskData ? ( + + ) : ( + getEmptyTagValue() + )} + + ), + }, + ]; + }, [hostRisk]); const column: DescriptionList[] = useMemo( () => [ @@ -273,7 +269,7 @@ export const HostOverview = React.memo( )} - {hostRiskScore && hostRiskLevel && ( + {isLicenseValid && ( `; -exports[`User Summary Component rendering it renders the panel view User Summary 1`] = ` +exports[`User Summary Component it renders the panel view User Summary 1`] = ` ({ - useUserRiskScore: jest.fn().mockReturnValue([ - true, - { - data: undefined, - isModuleEnabled: false, - }, - ]), -})); +const defaultProps = { + data: undefined, + inspect: null, + refetch: () => {}, + isModuleEnabled: true, + isLicenseValid: true, +}; + +jest.mock('../../../risk_score/containers/all'); + +const mockUseUserRiskScore = useUserRiskScore as jest.Mock; describe('User Summary Component', () => { - describe('rendering', () => { - const mockProps: UserSummaryProps = { - anomaliesData: mockAnomalies, - data: { - user: { - id: ['aa7ca589f1b8220002f2fc61c64cfbf1'], - name: ['username'], - domain: ['domain'], - }, - host: { - ip: ['10.142.0.7', 'fe80::4001:aff:fe8e:7'], - os: { - family: ['debian'], - name: ['Debian GNU/Linux'], - }, + const mockProps: UserSummaryProps = { + anomaliesData: mockAnomalies, + data: { + user: { + id: ['aa7ca589f1b8220002f2fc61c64cfbf1'], + name: ['username'], + domain: ['domain'], + }, + host: { + ip: ['10.142.0.7', 'fe80::4001:aff:fe8e:7'], + os: { + family: ['debian'], + name: ['Debian GNU/Linux'], }, }, - endDate: '2019-06-18T06:00:00.000Z', - id: 'userOverview', - isInDetailsSidePanel: false, - isLoadingAnomaliesData: false, - loading: false, - narrowDateRange: jest.fn(), - startDate: '2019-06-15T06:00:00.000Z', - userName: 'testUserName', - indexPatterns: [], + }, + endDate: '2019-06-18T06:00:00.000Z', + id: 'userOverview', + isInDetailsSidePanel: false, + isLoadingAnomaliesData: false, + loading: false, + narrowDateRange: jest.fn(), + startDate: '2019-06-15T06:00:00.000Z', + userName: 'testUserName', + indexPatterns: [], + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockUseUserRiskScore.mockReturnValue([true, { ...defaultProps, isModuleEnabled: false }]); + }); + + test('it renders the default User Summary', () => { + const wrapper = shallow( + + + + ); + + expect(wrapper.find('UserOverview')).toMatchSnapshot(); + }); + + test('it renders the panel view User Summary', () => { + const panelViewProps = { + ...mockProps, + isInDetailsSidePanel: true, + }; + + const wrapper = shallow( + + + + ); + + expect(wrapper.find('UserOverview')).toMatchSnapshot(); + }); + + test('it renders user risk score and level', () => { + const panelViewProps = { + ...mockProps, + isInDetailsSidePanel: true, }; + const risk = 'very high hos risk'; + const riskScore = 9999999; - test('it renders the default User Summary', () => { - const wrapper = shallow( - - - - ); - - expect(wrapper.find('UserOverview')).toMatchSnapshot(); - }); - - test('it renders the panel view User Summary', () => { - const panelViewProps = { - ...mockProps, - isInDetailsSidePanel: true, - }; - - const wrapper = shallow( - - - - ); - - expect(wrapper.find('UserOverview')).toMatchSnapshot(); - }); - - test('it renders user risk score and level', () => { - const panelViewProps = { - ...mockProps, - isInDetailsSidePanel: true, - }; - const risk = 'very high hos risk'; - const riskScore = 9999999; - - (useUserRiskScore as jest.Mock).mockReturnValue([ - false, - { - data: [ - { - user: { - name: 'testUsermame', - risk: { - rule_risks: [], - calculated_level: risk, - calculated_score_norm: riskScore, - }, + mockUseUserRiskScore.mockReturnValue([ + false, + { + ...defaultProps, + data: [ + { + user: { + name: 'testUsermame', + risk: { + rule_risks: [], + calculated_level: risk, + calculated_score_norm: riskScore, }, }, - ], - isModuleEnabled: true, - }, - ]); + }, + ], + }, + ]); - const { getByTestId } = render( - - - - ); + const { getByTestId } = render( + + + + ); - expect(getByTestId('user-risk-overview')).toHaveTextContent(risk); - expect(getByTestId('user-risk-overview')).toHaveTextContent(riskScore.toString()); - }); + expect(getByTestId('user-risk-overview')).toHaveTextContent(risk); + expect(getByTestId('user-risk-overview')).toHaveTextContent(riskScore.toString()); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx b/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx index 6c5f4a952e9a7..f0a639e92230a 100644 --- a/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx @@ -10,7 +10,7 @@ import { euiLightVars as lightTheme, euiDarkVars as darkTheme } from '@kbn/ui-th import { getOr } from 'lodash/fp'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; -import type { RiskSeverity } from '../../../../common/search_strategy'; +import { useGlobalTime } from '../../../common/containers/use_global_time'; import { buildUserNamesFilter } from '../../../../common/search_strategy'; import { DEFAULT_DARK_MODE } from '../../../../common/constants'; import type { DescriptionList } from '../../../../common/utility_types'; @@ -81,9 +81,13 @@ export const UserOverview = React.memo( () => (userName ? buildUserNamesFilter([userName]) : undefined), [userName] ); - const [_, { data: userRisk, isModuleEnabled }] = useUserRiskScore({ + + const { from, to } = useGlobalTime(); + + const [_, { data: userRisk, isLicenseValid }] = useUserRiskScore({ filterQuery, skip: userName == null, + timerange: { to, from }, }); const getDefaultRenderer = useCallback( @@ -91,7 +95,7 @@ export const UserOverview = React.memo( ), @@ -99,38 +103,32 @@ export const UserOverview = React.memo( ); const [userRiskScore, userRiskLevel] = useMemo(() => { - if (isModuleEnabled) { - const userRiskData = userRisk && userRisk.length > 0 ? userRisk[0] : undefined; - return [ - { - title: i18n.USER_RISK_SCORE, - description: ( - <> - {userRiskData - ? Math.round(userRiskData.user.risk.calculated_score_norm) - : getEmptyTagValue()} - - ), - }, - { - title: i18n.USER_RISK_CLASSIFICATION, - description: ( - <> - {userRiskData ? ( - - ) : ( - getEmptyTagValue() - )} - - ), - }, - ]; - } - return [undefined, undefined]; - }, [userRisk, isModuleEnabled]); + const userRiskData = userRisk && userRisk.length > 0 ? userRisk[0] : undefined; + return [ + { + title: i18n.USER_RISK_SCORE, + description: ( + <> + {userRiskData + ? Math.round(userRiskData.user.risk.calculated_score_norm) + : getEmptyTagValue()} + + ), + }, + { + title: i18n.USER_RISK_CLASSIFICATION, + description: ( + <> + {userRiskData ? ( + + ) : ( + getEmptyTagValue() + )} + + ), + }, + ]; + }, [userRisk]); const column = useMemo( () => [ @@ -255,7 +253,7 @@ export const UserOverview = React.memo( )} - {userRiskScore && userRiskLevel && ( + {isLicenseValid && ( { const { indicesExist, loading: isSourcererLoading, indexPattern } = useSourcererDataView(); + return ( <> {indicesExist ? ( <> - + ({ useSearchStrategy: jest.fn(), @@ -21,40 +21,77 @@ jest.mock('../../../common/hooks/use_space_id', () => ({ useSpaceId: jest.fn().mockReturnValue('default'), })); -jest.mock('../../../common/hooks/use_experimental_features', () => ({ - useIsExperimentalFeatureEnabled: jest.fn(), -})); - jest.mock('../../../common/hooks/use_app_toasts'); +jest.mock('../feature_status'); +const mockUseRiskScoreFeatureStatus = useRiskScoreFeatureStatus as jest.Mock; const mockUseSearchStrategy = useSearchStrategy as jest.Mock; -const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; const mockSearch = jest.fn(); -const mockRefetch = jest.fn(); let appToastsMock: jest.Mocked>; [useHostRiskScore, useUserRiskScore].forEach((fn) => { const riskEntity = fn.name === 'useHostRiskScore' ? 'host' : 'user'; + const defaultFeatureStatus = { + isLoading: false, + isDeprecated: false, + isLicenseValid: true, + isEnabled: true, + refetch: () => {}, + }; + const defaultRisk = { + data: undefined, + inspect: {}, + isInspected: false, + isLicenseValid: true, + isModuleEnabled: true, + isDeprecated: false, + totalCount: 0, + }; + const defaultSearchResponse = { + loading: false, + result: { + data: undefined, + totalCount: 0, + }, + search: mockSearch, + refetch: () => {}, + inspect: {}, + error: undefined, + }; describe(`${fn.name}`, () => { beforeEach(() => { jest.clearAllMocks(); appToastsMock = useAppToastsMock.create(); (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + mockUseRiskScoreFeatureStatus.mockReturnValue(defaultFeatureStatus); + mockUseSearchStrategy.mockReturnValue(defaultSearchResponse); }); - test('does not search if feature is not enabled', () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); - mockUseSearchStrategy.mockReturnValue({ - loading: false, - result: { - data: undefined, - totalCount: 0, + + test('does not search if license is not valid', () => { + mockUseRiskScoreFeatureStatus.mockReturnValue({ + ...defaultFeatureStatus, + isLicenseValid: false, + }); + const { result } = renderHook(() => fn(), { + wrapper: TestProviders, + }); + expect(mockSearch).not.toHaveBeenCalled(); + expect(result.current).toEqual([ + false, + { + ...defaultRisk, + isLicenseValid: false, + refetch: result.current[1].refetch, }, - search: mockSearch, - refetch: mockRefetch, - inspect: {}, - error: undefined, + ]); + }); + test('does not search if feature is not enabled', () => { + mockUseRiskScoreFeatureStatus.mockReturnValue({ + ...defaultFeatureStatus, + isEnabled: false, }); + const { result } = renderHook(() => fn(), { wrapper: TestProviders, }); @@ -62,57 +99,40 @@ let appToastsMock: jest.Mocked>; expect(result.current).toEqual([ false, { - data: undefined, - inspect: {}, - isInspected: false, + ...defaultRisk, isModuleEnabled: false, - refetch: mockRefetch, - totalCount: 0, + refetch: result.current[1].refetch, }, ]); }); - test('if query skipped and feature is enabled, isModuleEnabled should be true', () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); - mockUseSearchStrategy.mockReturnValue({ - loading: false, - result: { - data: undefined, - totalCount: 0, - }, - search: mockSearch, - refetch: mockRefetch, - inspect: {}, - error: undefined, + test('does not search if index is deprecated ', () => { + mockUseRiskScoreFeatureStatus.mockReturnValue({ + ...defaultFeatureStatus, + isDeprecated: true, }); const { result } = renderHook(() => fn({ skip: true }), { wrapper: TestProviders, }); + expect(mockSearch).not.toHaveBeenCalled(); expect(result.current).toEqual([ false, { - data: undefined, - inspect: {}, - isInspected: false, - isModuleEnabled: true, - refetch: mockRefetch, - totalCount: 0, + ...defaultRisk, + isDeprecated: true, + refetch: result.current[1].refetch, }, ]); }); test('handle index not found error', () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); - + mockUseRiskScoreFeatureStatus.mockReturnValue({ + ...defaultFeatureStatus, + isDeprecated: false, + isEnabled: false, + }); mockUseSearchStrategy.mockReturnValue({ - loading: false, - result: { - data: undefined, - totalCount: 0, - }, - search: mockSearch, - refetch: mockRefetch, - inspect: {}, + ...defaultSearchResponse, error: { attributes: { caused_by: { @@ -126,30 +146,14 @@ let appToastsMock: jest.Mocked>; }); expect(result.current).toEqual([ false, - { - data: undefined, - inspect: {}, - isInspected: false, - isModuleEnabled: false, - refetch: mockRefetch, - totalCount: 0, - }, + { ...defaultRisk, isModuleEnabled: false, refetch: result.current[1].refetch }, ]); }); test('show error toast', () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); - const error = new Error(); mockUseSearchStrategy.mockReturnValue({ - loading: false, - result: { - data: undefined, - totalCount: 0, - }, - search: mockSearch, - refetch: mockRefetch, - inspect: {}, + ...defaultSearchResponse, error, }); renderHook(() => fn(), { @@ -160,44 +164,23 @@ let appToastsMock: jest.Mocked>; }); }); - test('runs search if feature is enabled', () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); - mockUseSearchStrategy.mockReturnValue({ - loading: false, - result: { - data: [], - totalCount: 0, - }, - search: mockSearch, - refetch: mockRefetch, - inspect: {}, - error: undefined, - }); + test('runs search if feature is enabled and not deprecated', () => { renderHook(() => fn(), { wrapper: TestProviders, }); expect(mockSearch).toHaveBeenCalledWith({ defaultIndex: [`ml_${riskEntity}_risk_score_latest_default`], factoryQueryType: `${riskEntity}sRiskScore`, - filterQuery: undefined, - pagination: undefined, - timerange: undefined, - sort: undefined, }); }); test('return result', async () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); mockUseSearchStrategy.mockReturnValue({ - loading: false, + ...defaultSearchResponse, result: { data: [], totalCount: 0, }, - search: mockSearch, - refetch: mockRefetch, - inspect: {}, - error: undefined, }); const { result, waitFor } = renderHook(() => fn(), { wrapper: TestProviders, @@ -206,12 +189,9 @@ let appToastsMock: jest.Mocked>; expect(result.current).toEqual([ false, { + ...defaultRisk, data: [], - inspect: {}, - isInspected: false, - isModuleEnabled: true, - refetch: mockRefetch, - totalCount: 0, + refetch: result.current[1].refetch, }, ]); }); diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx index ef58555629c81..e1f1b7ecd13ba 100644 --- a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx +++ b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx @@ -5,8 +5,9 @@ * 2.0. */ -import { useEffect, useMemo } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; +import { useRiskScoreFeatureStatus } from '../feature_status'; import { createFilter } from '../../../common/containers/helpers'; import type { RiskScoreSortField, StrategyResponseType } from '../../../../common/search_strategy'; import { @@ -20,7 +21,6 @@ import * as i18n from './translations'; import type { InspectResponse } from '../../../types'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import { isIndexNotFoundError } from '../../../common/utils/exceptions'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import type { inputsModel } from '../../../common/store'; import { useSpaceId } from '../../../common/hooks/use_space_id'; import { useSearchStrategy } from '../../../common/containers/use_search_strategy'; @@ -31,7 +31,9 @@ export interface RiskScoreState extends UseRiskScoreParams { defaultIndex: string | undefined; factoryQueryType: T; - featureEnabled: boolean; } export const initialResult: Omit< @@ -66,7 +67,6 @@ export const useHostRiskScore = (params?: UseRiskScoreParams) => { const { timerange, onlyLatest, filterQuery, sort, skip = false, pagination } = params ?? {}; const spaceId = useSpaceId(); const defaultIndex = spaceId ? getHostRiskIndex(spaceId, onlyLatest) : undefined; - const riskyHostsFeatureEnabled = useIsExperimentalFeatureEnabled('riskyHostsEnabled'); return useRiskScore({ timerange, @@ -75,7 +75,6 @@ export const useHostRiskScore = (params?: UseRiskScoreParams) => { sort, skip, pagination, - featureEnabled: riskyHostsFeatureEnabled, defaultIndex, factoryQueryType: RiskQueries.hostsRiskScore, }); @@ -86,7 +85,6 @@ export const useUserRiskScore = (params?: UseRiskScoreParams) => { const spaceId = useSpaceId(); const defaultIndex = spaceId ? getUserRiskIndex(spaceId, onlyLatest) : undefined; - const riskyUsersFeatureEnabled = useIsExperimentalFeatureEnabled('riskyUsersEnabled'); return useRiskScore({ timerange, onlyLatest, @@ -94,7 +92,6 @@ export const useUserRiskScore = (params?: UseRiskScoreParams) => { sort, skip, pagination, - featureEnabled: riskyUsersFeatureEnabled, defaultIndex, factoryQueryType: RiskQueries.usersRiskScore, }); @@ -106,7 +103,6 @@ const useRiskScore = ): [boolean, RiskScoreState] => { @@ -114,6 +110,14 @@ const useRiskScore = { + if (defaultIndex) { + refetchDeprecated(defaultIndex); + refetch(); + } + }, [defaultIndex, refetch, refetchDeprecated]); + + // since query does not take timerange arg, we need to manually refetch when time range updates + // the results can be different if the user has run the ML for the first time since pressing refresh + useEffect(() => { + refetchAll(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [timerange?.to, timerange?.from]); const riskScoreResponse = useMemo( () => ({ data: response.data, inspect, - refetch, + refetch: refetchAll, totalCount: response.totalCount, - isModuleEnabled: skip ? featureEnabled : featureEnabled && response.data != null, + isLicenseValid, + isDeprecated, + isModuleEnabled: isEnabled, isInspected: false, }), - [featureEnabled, inspect, refetch, response.data, response.totalCount, skip] + [ + inspect, + isDeprecated, + isEnabled, + isLicenseValid, + refetchAll, + response.data, + response.totalCount, + ] ); const riskScoreRequest = useMemo( @@ -154,13 +181,10 @@ const useRiskScore = { @@ -172,10 +196,10 @@ const useRiskScore = { - if (!skip && riskScoreRequest != null && featureEnabled) { + if (!skip && riskScoreRequest != null && isLicenseValid && isEnabled && !isDeprecated) { search(riskScoreRequest); } - }, [featureEnabled, riskScoreRequest, search, skip]); + }, [isEnabled, isDeprecated, isLicenseValid, riskScoreRequest, search, skip]); - return [loading, riskScoreResponse]; + return [loading || isDeprecatedLoading, riskScoreResponse]; }; diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/api.ts b/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/api.ts new file mode 100644 index 0000000000000..1f4c236eb44b6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/api.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 { KibanaServices } from '../../../common/lib/kibana'; +import { RISK_SCORE_INDEX_STATUS_API_URL } from '../../../../common/constants'; + +// TODO: Future PR, make this a hard type everywhere relevant +export const enum RiskEntity { + 'user' = 'user', + 'host' = 'host', +} + +export const getRiskScoreIndexStatus = async (params: { + query: { + indexName: string; + entity: RiskEntity; + }; + signal?: AbortSignal; +}): Promise<{ + isDeprecated: boolean; + isEnabled: boolean; +}> => { + const { indexName, entity } = params.query; + return KibanaServices.get().http.fetch<{ isDeprecated: boolean; isEnabled: boolean }>( + RISK_SCORE_INDEX_STATUS_API_URL, + { + method: 'GET', + query: { indexName, entity }, + asSystemRequest: true, + signal: params.signal, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/index.test.ts b/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/index.test.ts new file mode 100644 index 0000000000000..1d62375e9769e --- /dev/null +++ b/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/index.test.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { act, renderHook } from '@testing-library/react-hooks'; +import { TestProviders } from '../../../common/mock'; + +import { useRiskScoreFeatureStatus } from '.'; +import { RiskQueries } from '../../../../common/search_strategy'; +import { useFetch } from '../../../common/hooks/use_fetch'; +import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities'; +import { RiskEntity } from './api'; +jest.mock('../../../common/hooks/use_fetch'); +jest.mock('../../../common/components/ml/hooks/use_ml_capabilities'); + +const mockFetch = jest.fn(); +const mockUseMlCapabilities = useMlCapabilities as jest.Mock; +const mockUseFetch = useFetch as jest.Mock; + +describe(`risk score feature status`, () => { + beforeEach(() => { + jest.clearAllMocks(); + mockUseMlCapabilities.mockReturnValue({ isPlatinumOrTrialLicense: true }); + mockUseFetch.mockReturnValue(defaultFetch); + }); + + const defaultFetch = { + data: undefined, + error: undefined, + fetch: mockFetch, + isLoading: false, + refetch: () => {}, + }; + const defaultResult = { + error: undefined, + isDeprecated: true, + isLicenseValid: true, + isEnabled: true, + isLoading: false, + }; + + test('does not search if license is not valid, and initial isDeprecated state is false', () => { + mockUseMlCapabilities.mockReturnValue({ isPlatinumOrTrialLicense: false }); + const { result } = renderHook( + () => useRiskScoreFeatureStatus(RiskQueries.hostsRiskScore, 'the_right_one'), + { + wrapper: TestProviders, + } + ); + expect(mockFetch).not.toHaveBeenCalled(); + expect(result.current).toEqual({ + ...defaultResult, + isLicenseValid: false, + isDeprecated: false, + isEnabled: false, + refetch: result.current.refetch, + }); + }); + + test('runs search if feature is enabled, and initial isDeprecated state is true', () => { + const { result } = renderHook( + () => useRiskScoreFeatureStatus(RiskQueries.hostsRiskScore, 'the_right_one'), + { + wrapper: TestProviders, + } + ); + expect(mockFetch).toHaveBeenCalledWith({ + query: { entity: RiskEntity.host, indexName: 'the_right_one' }, + }); + expect(result.current).toEqual({ + ...defaultResult, + refetch: result.current.refetch, + }); + }); + + test('updates state after search returns isDeprecated = false', () => { + const { result, rerender } = renderHook( + () => useRiskScoreFeatureStatus(RiskQueries.hostsRiskScore, 'the_right_one'), + { + wrapper: TestProviders, + } + ); + expect(result.current).toEqual({ + ...defaultResult, + refetch: result.current.refetch, + }); + mockUseFetch.mockReturnValue({ + ...defaultFetch, + data: { + isDeprecated: false, + isEnabled: true, + }, + }); + act(() => rerender()); + expect(result.current).toEqual({ + ...defaultResult, + isDeprecated: false, + refetch: result.current.refetch, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/index.ts b/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/index.ts new file mode 100644 index 0000000000000..c437d554b95e6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/risk_score/containers/feature_status/index.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useCallback, useEffect, useMemo } from 'react'; +import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities'; +import { REQUEST_NAMES, useFetch } from '../../../common/hooks/use_fetch'; +import { RiskQueries } from '../../../../common/search_strategy'; +import { getRiskScoreIndexStatus, RiskEntity } from './api'; + +interface RiskScoresFeatureStatus { + error: unknown; + // Is transform index an old version? + isDeprecated: boolean; + // does the transform index exist? + isEnabled: boolean; + // is the user's license platinum? + isLicenseValid: boolean; + isLoading: boolean; + refetch: (indexName: string) => void; +} + +export const useRiskScoreFeatureStatus = ( + factoryQueryType: RiskQueries.hostsRiskScore | RiskQueries.usersRiskScore, + defaultIndex?: string +): RiskScoresFeatureStatus => { + const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; + const entity = useMemo( + () => (factoryQueryType === RiskQueries.hostsRiskScore ? RiskEntity.host : RiskEntity.user), + [factoryQueryType] + ); + + const { fetch, data, isLoading, error } = useFetch( + REQUEST_NAMES.GET_RISK_SCORE_DEPRECATED, + getRiskScoreIndexStatus + ); + + const response = useMemo( + // if license is enabled, let isDeprecated = true so the actual + // risk score fetch is not called until this check is complete + () => + data ? data : { isDeprecated: isPlatinumOrTrialLicense, isEnabled: isPlatinumOrTrialLicense }, + // isPlatinumOrTrialLicense is initial state, not update requirement + // eslint-disable-next-line react-hooks/exhaustive-deps + [data] + ); + + const searchIndexStatus = useCallback( + (indexName: string) => { + fetch({ + query: { indexName, entity }, + }); + }, + [entity, fetch] + ); + + useEffect(() => { + if (isPlatinumOrTrialLicense && defaultIndex != null) { + searchIndexStatus(defaultIndex); + } + }, [isPlatinumOrTrialLicense, defaultIndex, searchIndexStatus]); + + return { + error, + isLoading, + refetch: searchIndexStatus, + isLicenseValid: isPlatinumOrTrialLicense, + ...response, + }; +}; diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/index.ts b/x-pack/plugins/security_solution/public/risk_score/containers/index.ts index 323a6d26acb34..605bbd7d0d6b1 100644 --- a/x-pack/plugins/security_solution/public/risk_score/containers/index.ts +++ b/x-pack/plugins/security_solution/public/risk_score/containers/index.ts @@ -5,7 +5,10 @@ * 2.0. */ -import type { HostRiskScore } from '../../../common/search_strategy/security_solution/risk_score'; +import type { + HostRiskScore, + UserRiskScore, +} from '../../../common/search_strategy/security_solution/risk_score'; export * from './all'; export * from './kpi'; @@ -24,6 +27,12 @@ export const enum HostRiskScoreQueryId { export interface HostRisk { loading: boolean; - isModuleEnabled?: boolean; + isModuleEnabled: boolean; result?: HostRiskScore[]; } + +export interface UserRisk { + loading: boolean; + isModuleEnabled: boolean; + result?: UserRiskScore[]; +} diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/kpi/index.tsx b/x-pack/plugins/security_solution/public/risk_score/containers/kpi/index.tsx index 6d4ee5c73c5f6..2c137a8bf1e06 100644 --- a/x-pack/plugins/security_solution/public/risk_score/containers/kpi/index.tsx +++ b/x-pack/plugins/security_solution/public/risk_score/containers/kpi/index.tsx @@ -28,9 +28,9 @@ import { import { useKibana } from '../../../common/lib/kibana'; import { isIndexNotFoundError } from '../../../common/utils/exceptions'; import type { ESTermQuery } from '../../../../common/typed_json'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import type { SeverityCount } from '../../../common/components/severity/types'; import { useSpaceId } from '../../../common/hooks/use_space_id'; +import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities'; type GetHostRiskScoreProps = KpiRiskScoreRequestOptions & { data: DataPublicPluginStart; @@ -93,14 +93,14 @@ export const useUserRiskScoreKpi = ({ }: UseUserRiskScoreKpiProps): RiskScoreKpi => { const spaceId = useSpaceId(); const defaultIndex = spaceId ? getUserRiskIndex(spaceId) : undefined; - const riskyUsersFeatureEnabled = useIsExperimentalFeatureEnabled('riskyUsersEnabled'); + const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; return useRiskScoreKpi({ filterQuery, skip, defaultIndex, entity: RiskScoreEntity.user, - featureEnabled: riskyUsersFeatureEnabled, + featureEnabled: isPlatinumOrTrialLicense, }); }; @@ -110,14 +110,14 @@ export const useHostRiskScoreKpi = ({ }: UseHostRiskScoreKpiProps): RiskScoreKpi => { const spaceId = useSpaceId(); const defaultIndex = spaceId ? getHostRiskIndex(spaceId) : undefined; - const riskyHostsFeatureEnabled = useIsExperimentalFeatureEnabled('riskyHostsEnabled'); + const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; return useRiskScoreKpi({ filterQuery, skip, defaultIndex, entity: RiskScoreEntity.host, - featureEnabled: riskyHostsFeatureEnabled, + featureEnabled: isPlatinumOrTrialLicense, }); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/flyout/__snapshots__/index.test.tsx.snap index a6a846110f72c..df6539a20c74e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/__snapshots__/index.test.tsx.snap @@ -18,171 +18,7 @@ exports[`Flyout rendering it renders correctly against snapshot 1`] = `
- .c7 { - -webkit-transition: background-color 0.7s ease; - transition: background-color 0.7s ease; - width: 100%; - height: 100%; -} - -.c7 .flyout-overlay .euiPanel { - background-color: #16171c; -} - -.c7 > div.timeline-drop-area .drop-and-provider-timeline { - display: none; -} - -.c7 > div.timeline-drop-area + div { - display: none !important; -} - -.c13 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - border-radius: 100%; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - font-size: 9px; - height: 34px; - -webkit-box-pack: center; - -webkit-justify-content: center; - -ms-flex-pack: center; - justify-content: center; - margin: 0 5px 0 5px; - padding: 7px 6px 4px 6px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - width: 34px; -} - -.c13 .euiBadge__content { - position: relative; - top: -1px; -} - -.c13 .euiBadge__text { - text-overflow: clip; -} - -.c10 { - overflow: hidden; - margin: 5px 0 5px 0; - padding: 3px; - white-space: nowrap; -} - -.c12 { - height: 20px; - margin: 0 5px 0 5px; - maxwidth: 85px; - minwidth: 85px; -} - -.c11 { - background-color: #343741; -} - -.c8 { - width: auto; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-flex-wrap: wrap; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - -webkit-box-pack: center; - -webkit-justify-content: center; - -ms-flex-pack: center; - justify-content: center; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - -webkit-align-content: center; - -ms-flex-line-pack: center; - align-content: center; - min-height: 100px; -} - -.c8 + div { - display: none !important; -} - -.c9 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: row; - -ms-flex-direction: row; - flex-direction: row; - -webkit-flex-wrap: no-wrap; - -ms-flex-wrap: no-wrap; - flex-wrap: no-wrap; -} - -.c5 { - padding: 2px 0 4px 0; -} - -.is-dragging .c5 .drop-target-data-providers { - background: rgba(125,222,216,0.1); - border: 0.2rem dashed #7dded8; -} - -.is-dragging .c5 .drop-target-data-providers .timeline-drop-area-empty__text { - color: #7dded8; -} - -.is-dragging .c5 .drop-target-data-providers .euiFormHelpText { - color: #7dded8; -} - -.c6 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: start; - -webkit-justify-content: flex-start; - -ms-flex-pack: start; - justify-content: flex-start; - padding-bottom: 2px; - position: relative; - border: 0.2rem dashed #535966; - border-radius: 5px; - padding: 4px 0; - margin: 2px 0 2px 0; - max-height: 33vh; - min-height: 100px; - overflow: auto; - resize: vertical; - background-color: #16171c; -} - -.c2 { + .c2 { display: block; } @@ -200,16 +36,6 @@ exports[`Flyout rendering it renders correctly against snapshot 1`] = ` overflow: hidden; } -.c4 { - border-radius: 0; - padding: 0 4px 0 4px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - z-index: 9000; -} -
-
-
-
-
-
-
-
- Drop anything -
- - - - - highlighted - - - - -
- here to build an -
- - - - OR - - - -
- query -
-
-
-
- -
-
-
-
-
-
-
`; diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.test.tsx index 366b8d36fc7d4..6c97e250a8e79 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.test.tsx @@ -9,91 +9,36 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock/test_providers'; -import { TimelineTabs } from '../../../../../common/types/timeline'; import { FlyoutBottomBar } from '.'; describe('FlyoutBottomBar', () => { test('it renders the expected bottom bar', () => { render( - + ); expect(screen.getByTestId('flyoutBottomBar')).toBeInTheDocument(); }); - test('it renders the data providers drop target area', () => { - render( - - - - ); - - expect(screen.getByTestId('dataProviders')).toBeInTheDocument(); - }); - test('it renders the flyout header panel', () => { render( - + ); expect(screen.getByTestId('timeline-flyout-header-panel')).toBeInTheDocument(); }); - test('it hides the data providers drop target area', () => { - render( - - - - ); - - expect(screen.queryByTestId('dataProviders')).not.toBeInTheDocument(); - }); - test('it hides the flyout header panel', () => { render( - + ); expect(screen.queryByTestId('timeline-flyout-header-panel')).not.toBeInTheDocument(); }); - - test('it renders the data providers drop target area when showDataproviders=false and tab is not query', () => { - render( - - - - ); - - expect(screen.getByTestId('dataProviders')).toBeInTheDocument(); - }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.tsx index 3bb2d4d7530c9..0b5286f1fedb3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/bottom_bar/index.tsx @@ -4,39 +4,20 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { EuiPanel } from '@elastic/eui'; import React from 'react'; -import styled from 'styled-components'; - -import { DataProviders } from '../../timeline/data_providers'; import { FLYOUT_BUTTON_BAR_CLASS_NAME } from '../../timeline/helpers'; import { FlyoutHeaderPanel } from '../header'; -import { TimelineTabs } from '../../../../../common/types/timeline'; - -const DataProvidersPanel = styled(EuiPanel)` - border-radius: 0; - padding: 0 4px 0 4px; - user-select: none; - z-index: ${({ theme }) => theme.eui.euiZLevel9}; -`; interface FlyoutBottomBarProps { - activeTab: TimelineTabs; - showDataproviders: boolean; + showTimelineHeaderPanel: boolean; timelineId: string; } export const FlyoutBottomBar = React.memo( - ({ activeTab, showDataproviders, timelineId }) => { + ({ showTimelineHeaderPanel, timelineId }) => { return (
- {showDataproviders && } - {(showDataproviders || (!showDataproviders && activeTab !== TimelineTabs.query)) && ( - - - - )} + {showTimelineHeaderPanel && }
); } diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx index 2b77901b4f102..dfb2fd0cdd927 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx @@ -29,7 +29,6 @@ const FlyoutComponent: React.FC = ({ timelineId, onAppLeave }) => { const dispatch = useDispatch(); const getTimelineShowStatus = useMemo(() => getTimelineShowStatusByIdSelector(), []); const { - activeTab, show, status: timelineStatus, updated, @@ -122,7 +121,7 @@ const FlyoutComponent: React.FC = ({ timelineId, onAppLeave }) => { - + ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx index 7ec4e5c2c942e..305eef7ef219d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx @@ -24,7 +24,6 @@ import { EventDetails } from '../../../../common/components/event_details/event_ import type { TimelineEventsDetailsItem } from '../../../../../common/search_strategy/timeline'; import * as i18n from './translations'; import { PreferenceFormattedDate } from '../../../../common/components/formatted_date'; -import type { HostRisk } from '../../../../risk_score/containers'; export type HandleOnEventClosed = () => void; interface Props { @@ -38,7 +37,6 @@ interface Props { rawEventData: object | undefined; timelineTabType: TimelineTabs | 'flyout'; timelineId: string; - hostRisk: HostRisk | null; handleOnEventClosed: HandleOnEventClosed; isReadOnly?: boolean; } @@ -107,7 +105,6 @@ export const ExpandableEvent = React.memo( isDraggable, loading, detailsData, - hostRisk, rawEventData, handleOnEventClosed, isReadOnly, @@ -133,7 +130,6 @@ export const ExpandableEvent = React.memo( rawEventData={rawEventData} timelineId={timelineId} timelineTabType={timelineTabType} - hostRisk={hostRisk} handleOnEventClosed={handleOnEventClosed} isReadOnly={isReadOnly} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/body.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/body.tsx index 58d09a6450017..25d7371c29303 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/body.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/body.tsx @@ -16,7 +16,6 @@ import type { } from '../../../../../../common/search_strategy'; import type { HandleOnEventClosed } from '../expandable_event'; import { ExpandableEvent } from '../expandable_event'; -import type { HostRisk } from '../../../../../risk_score/containers'; const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` .euiFlyoutBody__overflow { @@ -40,7 +39,6 @@ interface FlyoutBodyComponentProps { handleIsolationActionSuccess: () => void; handleOnEventClosed: HandleOnEventClosed; hostName: string; - hostRisk: HostRisk | null; isAlert: boolean; isDraggable?: boolean; isReadOnly?: boolean; @@ -61,7 +59,6 @@ const FlyoutBodyComponent = ({ handleIsolationActionSuccess, handleOnEventClosed, hostName, - hostRisk, isAlert, isDraggable, isReadOnly, @@ -100,7 +97,6 @@ const FlyoutBodyComponent = ({ rawEventData={rawEventData} timelineId={timelineId} timelineTabType="flyout" - hostRisk={hostRisk} handleOnEventClosed={handleOnEventClosed} isReadOnly={isReadOnly} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx index 22e4a8c568d19..0704bee217b15 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx @@ -9,9 +9,6 @@ import { EntityType, TimelineId } from '@kbn/timelines-plugin/common'; import { noop } from 'lodash/fp'; import React, { useCallback, useMemo, useState } from 'react'; -import { buildHostNamesFilter } from '../../../../../../common/search_strategy'; -import type { HostRisk } from '../../../../../risk_score/containers'; -import { useHostRiskScore } from '../../../../../risk_score/containers'; import { useHostIsolationTools } from '../use_host_isolation_tools'; import { FlyoutHeaderContent } from './header'; import { FlyoutBody } from './body'; @@ -45,35 +42,6 @@ export const useToGetInternalFlyout = () => { const { alertId, isAlert, hostName, ruleName, timestamp } = useBasicDataFromDetailsData(detailsData); - const filterQuery = useMemo( - () => (hostName ? buildHostNamesFilter([hostName]) : undefined), - [hostName] - ); - - const pagination = useMemo( - () => ({ - cursorStart: 0, - querySize: 1, - }), - [] - ); - const [hostRiskLoading, { data, isModuleEnabled }] = useHostRiskScore({ - filterQuery, - pagination, - }); - - const hostRisk: HostRisk | null = useMemo( - () => - data - ? { - loading: hostRiskLoading, - isModuleEnabled, - result: data, - } - : null, - [data, hostRiskLoading, isModuleEnabled] - ); - const { isolateAction, isHostIsolationPanelOpen, @@ -99,7 +67,6 @@ export const useToGetInternalFlyout = () => { detailsData={detailsData} event={{ eventId: localAlert._id, indexName: localAlert._index }} hostName={hostName ?? ''} - hostRisk={hostRisk} handleIsolationActionSuccess={handleIsolationActionSuccess} handleOnEventClosed={noop} isAlert={isAlert} @@ -121,7 +88,6 @@ export const useToGetInternalFlyout = () => { detailsData, handleIsolationActionSuccess, hostName, - hostRisk, isAlert, isHostIsolationPanelOpen, isIsolateActionSuccessBannerVisible, diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx index 065ac297ee468..6bee089027477 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx @@ -16,6 +16,7 @@ export interface GetBasicDataFromDetailsData { agentId?: string; isAlert: boolean; hostName: string; + userName: string; ruleName: string; timestamp: string; } @@ -42,6 +43,11 @@ export const useBasicDataFromDetailsData = ( [data] ); + const userName = useMemo( + () => getFieldValue({ category: 'user', field: 'user.name' }, data), + [data] + ); + const timestamp = useMemo( () => getFieldValue({ category: 'base', field: '@timestamp' }, data), [data] @@ -53,10 +59,11 @@ export const useBasicDataFromDetailsData = ( agentId, isAlert, hostName, + userName, ruleName, timestamp, }), - [agentId, alertId, hostName, isAlert, ruleName, timestamp] + [agentId, alertId, hostName, isAlert, ruleName, timestamp, userName] ); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.test.tsx index 7044ee548c3af..30713af95df54 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.test.tsx @@ -103,6 +103,13 @@ jest.mock('../../../../risk_score/containers', () => { isModuleEnabled: false, }, ]), + useUserRiskScore: jest.fn().mockReturnValue([ + true, + { + data: undefined, + isModuleEnabled: false, + }, + ]), }; }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx index 00397ea43e59a..11a20a0c64f0d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx @@ -15,9 +15,6 @@ import type { BrowserFields } from '../../../../common/containers/source'; import { ExpandableEvent, ExpandableEventTitle } from './expandable_event'; import { useTimelineEventsDetails } from '../../../containers/details'; import type { TimelineTabs } from '../../../../../common/types/timeline'; -import { buildHostNamesFilter } from '../../../../../common/search_strategy'; -import type { HostRisk } from '../../../../risk_score/containers'; -import { useHostRiskScore } from '../../../../risk_score/containers'; import { useHostIsolationTools } from './use_host_isolation_tools'; import { FlyoutBody, FlyoutHeader, FlyoutFooter } from './flyout'; import { useBasicDataFromDetailsData, getAlertIndexAlias } from './helpers'; @@ -79,34 +76,6 @@ const EventDetailsPanelComponent: React.FC = ({ const { alertId, isAlert, hostName, ruleName, timestamp } = useBasicDataFromDetailsData(detailsData); - const filterQuery = useMemo( - () => (hostName ? buildHostNamesFilter([hostName]) : undefined), - [hostName] - ); - - const pagination = useMemo( - () => ({ - cursorStart: 0, - querySize: 1, - }), - [] - ); - - const [hostRiskLoading, { data, isModuleEnabled }] = useHostRiskScore({ - filterQuery, - pagination, - }); - - const hostRisk: HostRisk | null = useMemo(() => { - return data - ? { - loading: hostRiskLoading, - isModuleEnabled, - result: data, - } - : null; - }, [data, hostRiskLoading, isModuleEnabled]); - const header = useMemo( () => isFlyoutView || isHostIsolationPanelOpen ? ( @@ -149,7 +118,6 @@ const EventDetailsPanelComponent: React.FC = ({ detailsData={detailsData} event={expandedEvent} hostName={hostName} - hostRisk={hostRisk} handleIsolationActionSuccess={handleIsolationActionSuccess} handleOnEventClosed={handleOnEventClosed} isAlert={isAlert} @@ -198,7 +166,6 @@ const EventDetailsPanelComponent: React.FC = ({ rawEventData={rawEventData} timelineId={timelineId} timelineTabType={tabType} - hostRisk={hostRisk} handleOnEventClosed={handleOnEventClosed} /> @@ -212,7 +179,6 @@ const EventDetailsPanelComponent: React.FC = ({ handleIsolationActionSuccess, handleOnEventClosed, hostName, - hostRisk, isAlert, isDraggable, isFlyoutView, diff --git a/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx b/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx index 3c97f0ad49b4e..277403c5292f5 100644 --- a/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx +++ b/x-pack/plugins/security_solution/public/users/components/kpi_users/index.tsx @@ -19,11 +19,11 @@ import { RISKY_USERS_DOC_LINK } from '../constants'; export const UsersKpiComponent = React.memo( ({ filterQuery, from, indexNames, to, setQuery, skip, updateDateRange }) => { - const [_, { isModuleEnabled }] = useUserRiskScore(); + const [loading, { isLicenseValid, isModuleEnabled }] = useUserRiskScore(); return ( <> - {isModuleEnabled === false && ( + {isLicenseValid && !isModuleEnabled && !loading && ( <> = ({ @@ -61,7 +60,7 @@ const UsersDetailsComponent: React.FC = ({ usersDetailsPagePath, }) => { const dispatch = useDispatch(); - const riskyUsersFeatureEnabled = useIsExperimentalFeatureEnabled('riskyUsersEnabled'); + const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const graphEventId = useShallowEqualSelector( (state) => (getTimeline(state, TimelineId.hostsPageEvents) ?? timelineDefaults).graphEventId @@ -183,7 +182,7 @@ const UsersDetailsComponent: React.FC = ({ navTabs={navTabsUsersDetails( detailName, hasMlUserPermissions(capabilities), - riskyUsersFeatureEnabled + isPlatinumOrTrialLicense )} /> diff --git a/x-pack/plugins/security_solution/public/users/pages/details/nav_tabs.tsx b/x-pack/plugins/security_solution/public/users/pages/details/nav_tabs.tsx index 90bc856e0d058..588372272e540 100644 --- a/x-pack/plugins/security_solution/public/users/pages/details/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/details/nav_tabs.tsx @@ -10,6 +10,7 @@ import * as i18n from '../translations'; import type { UsersDetailsNavTab } from './types'; import { UsersTableType } from '../../store/model'; import { USERS_PATH } from '../../../../common/constants'; +import { TECHNICAL_PREVIEW } from '../../../overview/pages/translations'; const getTabsOnUsersDetailsUrl = (userName: string, tabName: UsersTableType) => `${USERS_PATH}/name/${userName}/${tabName}`; @@ -45,6 +46,10 @@ export const navTabsUsersDetails = ( name: i18n.NAVIGATION_RISK_TITLE, href: getTabsOnUsersDetailsUrl(userName, UsersTableType.risk), disabled: false, + isBeta: true, + betaOptions: { + text: TECHNICAL_PREVIEW, + }, }, }; diff --git a/x-pack/plugins/security_solution/public/users/pages/nav_tabs.tsx b/x-pack/plugins/security_solution/public/users/pages/nav_tabs.tsx index 0e5218090f162..801074b797bfb 100644 --- a/x-pack/plugins/security_solution/public/users/pages/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/nav_tabs.tsx @@ -10,6 +10,7 @@ import * as i18n from './translations'; import { UsersTableType } from '../store/model'; import type { UsersNavTab } from './navigation/types'; import { USERS_PATH } from '../../../common/constants'; +import { TECHNICAL_PREVIEW } from '../../overview/pages/translations'; const getTabsOnUsersUrl = (tabName: UsersTableType) => `${USERS_PATH}/${tabName}`; @@ -49,6 +50,10 @@ export const navTabsUsers = ( name: i18n.NAVIGATION_RISK_TITLE, href: getTabsOnUsersUrl(UsersTableType.risk), disabled: false, + isBeta: true, + betaOptions: { + text: TECHNICAL_PREVIEW, + }, }, }; diff --git a/x-pack/plugins/security_solution/public/users/pages/navigation/user_risk_score_tab_body.tsx b/x-pack/plugins/security_solution/public/users/pages/navigation/user_risk_score_tab_body.tsx index 52b8c60f605e7..c39e614c5fbb1 100644 --- a/x-pack/plugins/security_solution/public/users/pages/navigation/user_risk_score_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/navigation/user_risk_score_tab_body.tsx @@ -8,6 +8,9 @@ import React, { useEffect, useMemo, useState } from 'react'; import { noop } from 'lodash/fp'; +import { RiskEntity } from '../../../risk_score/containers/feature_status/api'; +import { useGlobalTime } from '../../../common/containers/use_global_time'; +import { RiskScoresDeprecated } from '../../../common/components/risk_score_deprecated'; import type { UsersComponentsQueryProps } from './types'; import { manageQuery } from '../../../common/components/page/manage_query'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; @@ -43,6 +46,7 @@ export const UserRiskScoreQueryTabBody = ({ }), [activePage, limit] ); + const { from, to } = useGlobalTime(); const { toggleStatus } = useQueryToggle(UserRiskScoreQueryId.USERS_BY_RISK); const [querySkip, setQuerySkip] = useState(skip || !toggleStatus); @@ -50,18 +54,24 @@ export const UserRiskScoreQueryTabBody = ({ setQuerySkip(skip || !toggleStatus); }, [skip, toggleStatus]); - const [loading, { data, totalCount, inspect, isInspected, refetch }] = useUserRiskScore({ - filterQuery, - skip: querySkip, - pagination, - sort, - }); + const [loading, { data, totalCount, inspect, isInspected, isDeprecated, refetch }] = + useUserRiskScore({ + filterQuery, + skip: querySkip, + pagination, + sort, + timerange: { to, from }, + }); const { severityCount, loading: isKpiLoading } = useUserRiskScoreKpi({ filterQuery, skip: querySkip, }); + if (isDeprecated) { + return ; + } + return ( (userName ? buildUserNamesFilter([userName]) : undefined), [userName] ); - const [loading, { data, refetch, inspect }] = useUserRiskScore({ + const [loading, { data, refetch, inspect, isDeprecated }] = useUserRiskScore({ filterQuery, onlyLatest: false, skip: !overTimeToggleStatus && !contributorsToggleStatus, @@ -87,6 +89,10 @@ const UserRiskTabBodyComponent: React.FC< [setOverTimeToggleStatus] ); + if (isDeprecated) { + return ; + } + const lastUsertRiskItem: UserRiskScore | null = data && data.length > 0 ? data[data.length - 1] : null; const rules = lastUsertRiskItem ? lastUsertRiskItem.user.risk.rule_risks : []; diff --git a/x-pack/plugins/security_solution/public/users/pages/users.tsx b/x-pack/plugins/security_solution/public/users/pages/users.tsx index 94345fa24f377..d3140a330da63 100644 --- a/x-pack/plugins/security_solution/public/users/pages/users.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/users.tsx @@ -50,7 +50,6 @@ import { generateSeverityFilter } from '../../hosts/store/helpers'; import { UsersTableType } from '../store/model'; import { hasMlUserPermissions } from '../../../common/machine_learning/has_ml_user_permissions'; import { useMlCapabilities } from '../../common/components/ml/hooks/use_ml_capabilities'; -import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { LandingPageComponent } from '../../common/components/landing_page'; import { userNameExistsFilter } from './details/helpers'; @@ -169,10 +168,10 @@ const UsersComponent = () => { ); const capabilities = useMlCapabilities(); - const riskyUsersFeatureEnabled = useIsExperimentalFeatureEnabled('riskyUsersEnabled'); + const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; const navTabs = useMemo( - () => navTabsUsers(hasMlUserPermissions(capabilities), riskyUsersFeatureEnabled), - [capabilities, riskyUsersFeatureEnabled] + () => navTabsUsers(hasMlUserPermissions(capabilities), isPlatinumOrTrialLicense), + [capabilities, isPlatinumOrTrialLicense] ); return ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts index 5a940ebe364c5..6777adc5de697 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts @@ -9,14 +9,14 @@ import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { QUERY_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; import { SERVER_APP_ID } from '../../../../../common/constants'; -import type { QueryRuleParams } from '../../schemas/rule_schemas'; -import { queryRuleParams } from '../../schemas/rule_schemas'; +import type { UnifiedQueryRuleParams } from '../../schemas/rule_schemas'; +import { unifiedQueryRuleParams } from '../../schemas/rule_schemas'; import { queryExecutor } from '../../signals/executors/query'; import type { CreateRuleOptions, SecurityAlertType } from '../types'; import { validateIndexPatterns } from '../utils'; export const createQueryAlertType = ( createOptions: CreateRuleOptions -): SecurityAlertType => { +): SecurityAlertType => { const { eventsTelemetry, experimentalFeatures, version } = createOptions; return { id: QUERY_RULE_TYPE_ID, @@ -24,7 +24,7 @@ export const createQueryAlertType = ( validate: { params: { validate: (object: unknown) => { - const [validated, errors] = validateNonExact(object, queryRuleParams); + const [validated, errors] = validateNonExact(object, unifiedQueryRuleParams); if (errors != null) { throw new Error(errors); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/saved_query/create_saved_query_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/saved_query/create_saved_query_alert_type.ts index 325d9adfb1bda..e4ab515b5ff32 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/saved_query/create_saved_query_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/saved_query/create_saved_query_alert_type.ts @@ -9,14 +9,14 @@ import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { SAVED_QUERY_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; import { SERVER_APP_ID } from '../../../../../common/constants'; -import type { CompleteRule, SavedQueryRuleParams } from '../../schemas/rule_schemas'; -import { savedQueryRuleParams } from '../../schemas/rule_schemas'; +import type { CompleteRule, UnifiedQueryRuleParams } from '../../schemas/rule_schemas'; +import { unifiedQueryRuleParams } from '../../schemas/rule_schemas'; import { queryExecutor } from '../../signals/executors/query'; import type { CreateRuleOptions, SecurityAlertType } from '../types'; import { validateIndexPatterns } from '../utils'; export const createSavedQueryAlertType = ( createOptions: CreateRuleOptions -): SecurityAlertType => { +): SecurityAlertType => { const { experimentalFeatures, version } = createOptions; return { id: SAVED_QUERY_RULE_TYPE_ID, @@ -24,7 +24,7 @@ export const createSavedQueryAlertType = ( validate: { params: { validate: (object: unknown) => { - const [validated, errors] = validateNonExact(object, savedQueryRuleParams); + const [validated, errors] = validateNonExact(object, unifiedQueryRuleParams); if (errors != null) { throw new Error(errors); } @@ -82,7 +82,7 @@ export const createSavedQueryAlertType = ( const result = await queryExecutor({ inputIndex, runtimeMappings, - completeRule: completeRule as CompleteRule, + completeRule: completeRule as CompleteRule, tuple, exceptionItems, experimentalFeatures, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts index d879f3dbeda3c..ca44042dc9d65 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts @@ -186,6 +186,12 @@ export const savedQueryRuleParams = t.intersection([baseRuleParams, savedQuerySp export type SavedQuerySpecificRuleParams = t.TypeOf; export type SavedQueryRuleParams = t.TypeOf; +export const unifiedQueryRuleParams = t.intersection([ + baseRuleParams, + t.union([querySpecificRuleParams, savedQuerySpecificRuleParams]), +]); +export type UnifiedQueryRuleParams = t.TypeOf; + const thresholdSpecificRuleParams = t.type({ type: t.literal('threshold'), language: nonEqlLanguages, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts index 48e979472c4c7..aa01b614d6bff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts @@ -18,11 +18,7 @@ import { getFilter } from '../get_filter'; import { searchAfterAndBulkCreate } from '../search_after_bulk_create'; import type { RuleRangeTuple, BulkCreate, WrapHits } from '../types'; import type { ITelemetryEventsSender } from '../../../telemetry/sender'; -import type { - CompleteRule, - SavedQueryRuleParams, - QueryRuleParams, -} from '../../schemas/rule_schemas'; +import type { CompleteRule, UnifiedQueryRuleParams } from '../../schemas/rule_schemas'; import type { ExperimentalFeatures } from '../../../../../common/experimental_features'; import { buildReasonMessageForQueryAlert } from '../reason_formatters'; import { withSecuritySpan } from '../../../../utils/with_security_span'; @@ -48,7 +44,7 @@ export const queryExecutor = async ({ }: { inputIndex: string[]; runtimeMappings: estypes.MappingRuntimeFields | undefined; - completeRule: CompleteRule | CompleteRule; + completeRule: CompleteRule; tuple: RuleRangeTuple; exceptionItems: ExceptionListItemSchema[]; listClient: ListClient; @@ -79,7 +75,6 @@ export const queryExecutor = async ({ return searchAfterAndBulkCreate({ tuple, - completeRule, services, listClient, exceptionsList: exceptionItems, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts index 7f8cc6bbb38db..6b347ce47688b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts @@ -85,6 +85,10 @@ export const getFilter = async ({ // any additional language, query, filters, etc... if (query != null && language != null && index != null) { return getQueryFilter(query, language, filters || [], index, lists); + } else if (savedId && index != null) { + // if savedId present and we ending up here, then saved query failed to be fetched + // and we also didn't fall back to saved in rule query + throw Error(`Failed to fetch saved query. "${err.message}"`); } else { // user did not give any additional fall back mechanism for generating a rule // rethrow error for activity monitoring diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index 9561d19fe4378..613104bc7604b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -201,7 +201,6 @@ describe('searchAfterAndBulkCreate', () => { const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ tuple, - completeRule: queryCompleteRule, listClient, exceptionsList: [exceptionItem], services: mockService, @@ -295,7 +294,6 @@ describe('searchAfterAndBulkCreate', () => { }, ]; const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ - completeRule: queryCompleteRule, tuple, listClient, exceptionsList: [exceptionItem], @@ -370,7 +368,6 @@ describe('searchAfterAndBulkCreate', () => { }, ]; const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ - completeRule: queryCompleteRule, tuple, listClient, exceptionsList: [exceptionItem], @@ -429,7 +426,6 @@ describe('searchAfterAndBulkCreate', () => { }, ]; const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ - completeRule: queryCompleteRule, tuple, listClient, exceptionsList: [exceptionItem], @@ -498,7 +494,6 @@ describe('searchAfterAndBulkCreate', () => { ); const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ - completeRule: queryCompleteRule, tuple, listClient, exceptionsList: [], @@ -553,7 +548,6 @@ describe('searchAfterAndBulkCreate', () => { }, ]; const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ - completeRule: queryCompleteRule, tuple, listClient, exceptionsList: [exceptionItem], @@ -622,7 +616,6 @@ describe('searchAfterAndBulkCreate', () => { }, ]; const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ - completeRule: queryCompleteRule, tuple, listClient, exceptionsList: [exceptionItem], @@ -693,7 +686,6 @@ describe('searchAfterAndBulkCreate', () => { ) ); const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ - completeRule: queryCompleteRule, tuple, listClient, exceptionsList: [], @@ -743,7 +735,6 @@ describe('searchAfterAndBulkCreate', () => { listClient, exceptionsList: [exceptionItem], tuple, - completeRule: queryCompleteRule, services: mockService, ruleExecutionLogger, eventsTelemetry: undefined, @@ -789,7 +780,6 @@ describe('searchAfterAndBulkCreate', () => { listClient, exceptionsList: [exceptionItem], tuple, - completeRule: queryCompleteRule, services: mockService, ruleExecutionLogger, eventsTelemetry: undefined, @@ -914,7 +904,6 @@ describe('searchAfterAndBulkCreate', () => { ); const { success, createdSignalsCount, lastLookBackDate, errors } = await searchAfterAndBulkCreate({ - completeRule: queryCompleteRule, tuple, listClient, exceptionsList: [], @@ -1001,7 +990,6 @@ describe('searchAfterAndBulkCreate', () => { const mockEnrichment = jest.fn((a) => a); const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({ enrichment: mockEnrichment, - completeRule: queryCompleteRule, tuple, listClient, exceptionsList: [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts index c941995eb3bc7..eb0bc7e740b2c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -27,7 +27,6 @@ import { withSecuritySpan } from '../../../utils/with_security_span'; export const searchAfterAndBulkCreate = async ({ buildReasonMessage, bulkCreate, - completeRule, enrichment = identity, eventsTelemetry, exceptionsList, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_event_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_event_signal.ts index a1a63b2e2493c..460b73b084356 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_event_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_event_signal.ts @@ -122,7 +122,6 @@ export const createEventSignal = async ({ const result = await searchAfterAndBulkCreate({ buildReasonMessage: buildReasonMessageForThreatMatchAlert, bulkCreate, - completeRule, enrichment: threatEnrichment, eventsTelemetry, exceptionsList: exceptionItems, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts index b063ef87761bc..442f9ce203405 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/create_threat_signal.ts @@ -72,7 +72,6 @@ export const createThreatSignal = async ({ const result = await searchAfterAndBulkCreate({ buildReasonMessage: buildReasonMessageForThreatMatchAlert, bulkCreate, - completeRule, enrichment: threatEnrichment, eventsTelemetry, exceptionsList: exceptionItems, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index 5609eed4c0801..1e2b6db4b4c81 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -26,13 +26,7 @@ import type { EqlSequence, } from '../../../../common/detection_engine/types'; import type { ITelemetryEventsSender } from '../../telemetry/sender'; -import type { - CompleteRule, - QueryRuleParams, - ThreatRuleParams, - RuleParams, - SavedQueryRuleParams, -} from '../schemas/rule_schemas'; +import type { RuleParams } from '../schemas/rule_schemas'; import type { GenericBulkCreateResponse } from '../rule_types/factories'; import type { BuildReasonMessage } from './reason_formatters'; import type { @@ -262,10 +256,6 @@ export interface SearchAfterAndBulkCreateParams { from: moment.Moment; maxSignals: number; }; - completeRule: - | CompleteRule - | CompleteRule - | CompleteRule; services: RuleExecutorServices; listClient: ListClient; exceptionsList: ExceptionListItemSchema[]; diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/readme.md b/x-pack/plugins/security_solution/server/lib/risk_score/readme.md new file mode 100644 index 0000000000000..e6f452cefdc95 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/risk_score/readme.md @@ -0,0 +1,22 @@ +# Risk Score API + +### API usage + +The risk score API has one route with one method + +1. GET - `getRiskScoreIndexStatusRoute` +2. REQUEST: + ```typescript + GET /internal/risk_score/index_status + { + indexName: 'ml_host_risk_score_latest' + } + ``` +3. RESPONSE: + ```typescript + { + isDeprecated: boolean; + isEnabled: boolean; + } + ``` +4. This route is called from `useRiskScore` hook. \ No newline at end of file diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/routes/index.test.ts b/x-pack/plugins/security_solution/server/lib/risk_score/routes/index.test.ts new file mode 100644 index 0000000000000..77adf7e0efef6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/risk_score/routes/index.test.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getRiskScoreIndexStatusRoute } from '.'; +import { + requestMock, + serverMock, + requestContextMock, +} from '../../detection_engine/routes/__mocks__'; + +import { RISK_SCORE_INDEX_STATUS_API_URL } from '../../../../common/constants'; + +const fieldCaps = { + indices: ['not_important'], + fields: { + 'host.risk.calculated_level': { + text: { + type: 'text', + metadata_field: false, + searchable: true, + aggregatable: false, + }, + }, + }, +}; +describe('risk score index status route', () => { + let server: ReturnType; + let { clients, context } = requestContextMock.createTools(); + const getRequest = (entity: string) => + requestMock.create({ + method: 'get', + path: RISK_SCORE_INDEX_STATUS_API_URL, + query: { indexName: 'hi', entity }, + }); + + beforeEach(() => { + server = serverMock.create(); + ({ clients, context } = requestContextMock.createTools()); + }); + + test('If new fields are not available, isDeprecated = true', async () => { + clients.clusterClient.asCurrentUser.fieldCaps.mockResolvedValue(fieldCaps); + getRiskScoreIndexStatusRoute(server.router); + const response = await server.inject( + getRequest('user'), + requestContextMock.convertContext(context) + ); + expect(response.body).toEqual({ isDeprecated: true, isEnabled: true }); + }); + test('If new fields are available, isDeprecated = false', async () => { + clients.clusterClient.asCurrentUser.fieldCaps.mockResolvedValue(fieldCaps); + getRiskScoreIndexStatusRoute(server.router); + const response = await server.inject( + getRequest('host'), + requestContextMock.convertContext(context) + ); + expect(response.body).toEqual({ isDeprecated: false, isEnabled: true }); + }); + + test('404 error does not throw, returns isEnabled = false', async () => { + const notFoundError: Error & { statusCode?: number } = new Error('not found'); + notFoundError.statusCode = 404; + clients.clusterClient.asCurrentUser.fieldCaps.mockRejectedValue(notFoundError); + getRiskScoreIndexStatusRoute(server.router); + const response = await server.inject( + getRequest('host'), + requestContextMock.convertContext(context) + ); + expect(response.body).toEqual({ isDeprecated: false, isEnabled: false }); + }); + + test('any other error throws', async () => { + clients.clusterClient.asCurrentUser.fieldCaps.mockRejectedValue(new Error('any other error')); + getRiskScoreIndexStatusRoute(server.router); + const response = await server.inject( + getRequest('host'), + requestContextMock.convertContext(context) + ); + expect(response.body).toEqual({ message: 'any other error', status_code: 500 }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/routes/index.ts b/x-pack/plugins/security_solution/server/lib/risk_score/routes/index.ts new file mode 100644 index 0000000000000..3036ce8db94db --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/risk_score/routes/index.ts @@ -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 { transformError } from '@kbn/securitysolution-es-utils'; + +import { RISK_SCORE_INDEX_STATUS_API_URL } from '../../../../common/constants'; +import type { SecuritySolutionPluginRouter } from '../../../types'; +import { buildRouteValidation } from '../../../utils/build_validation/route_validation'; +import { buildSiemResponse } from '../../detection_engine/routes/utils'; +import { indexStatusSchema } from './schema'; + +export const getRiskScoreIndexStatusRoute = (router: SecuritySolutionPluginRouter) => { + router.get( + { + path: RISK_SCORE_INDEX_STATUS_API_URL, + validate: { + query: buildRouteValidation(indexStatusSchema), + }, + options: { + tags: ['access:securitySolution'], + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + const coreContext = await context.core; + const { indexName, entity } = request.query; + try { + const newFieldName = `${entity}.risk.calculated_level`; + const res = await coreContext.elasticsearch.client.asCurrentUser.fieldCaps({ + index: indexName, + fields: newFieldName, + ignore_unavailable: true, + allow_no_indices: false, + }); + const isDeprecated = !Object.keys(res.fields).includes(newFieldName); + + return response.ok({ + body: { isDeprecated, isEnabled: true }, + }); + } catch (err) { + const error = transformError(err); + if (error.statusCode === 404) { + // index does not exist, therefore cannot be deprecated + return response.ok({ + body: { isDeprecated: false, isEnabled: false }, + }); + } + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/routes/schema.ts b/x-pack/plugins/security_solution/server/lib/risk_score/routes/schema.ts new file mode 100644 index 0000000000000..7cff67daaa647 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/risk_score/routes/schema.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; + +export const indexStatusSchema = t.type({ + indexName: t.string, + entity: t.string, +}); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts index cab2014861844..a07995029cd76 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts @@ -22,10 +22,13 @@ import { templateExceptionList, addDefaultAdvancedPolicyConfigSettings, metricsResponseToValueListMetaData, + tlog, + setIsElasticCloudDeployment, } from './helpers'; import type { ESClusterInfo, ESLicense, ExceptionListItem } from './types'; import type { PolicyConfig, PolicyData } from '../../../common/endpoint/types'; import { cloneDeep, set } from 'lodash'; +import { loggingSystemMock } from '@kbn/core/server/mocks'; describe('test diagnostic telemetry scheduled task timing helper', () => { test('test -5 mins is returned when there is no previous task run', async () => { @@ -907,3 +910,24 @@ describe('test metrics response to value list meta data', () => { }); }); }); + +describe('test tlog', () => { + let logger: ReturnType; + + beforeEach(() => { + logger = loggingSystemMock.createLogger(); + }); + + test('should log when cloud', () => { + setIsElasticCloudDeployment(true); + tlog(logger, 'test'); + expect(logger.info).toHaveBeenCalled(); + setIsElasticCloudDeployment(false); + }); + + test('should NOT log when on prem', () => { + tlog(logger, 'test'); + expect(logger.info).toHaveBeenCalledTimes(0); + expect(logger.debug).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts index 1a78ea3352aa7..0c42a35a317e7 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts @@ -9,6 +9,7 @@ import moment from 'moment'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import type { PackagePolicy } from '@kbn/fleet-plugin/common/types/models/package_policy'; import { merge } from 'lodash'; +import type { Logger } from '@kbn/core/server'; import { copyAllowlistedFields, exceptionListAllowlistFields } from './filterlists'; import type { PolicyConfig, PolicyData } from '../../../common/endpoint/types'; import type { @@ -266,3 +267,16 @@ export const metricsResponseToValueListMetaData = ({ used_in_indicator_match_rule_count: indicatorMatchMetricsResponse?.aggregations?.vl_used_in_indicator_match_rule_count?.value ?? 0, }); + +export let isElasticCloudDeployment = false; +export const setIsElasticCloudDeployment = (value: boolean) => { + isElasticCloudDeployment = value; +}; + +export const tlog = (logger: Logger, message: string) => { + if (isElasticCloudDeployment) { + logger.info(message); + } else { + logger.debug(message); + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/preview_sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/preview_sender.ts index 08aee614a3cfc..2c6a55900a14d 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/preview_sender.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/preview_sender.ts @@ -18,6 +18,7 @@ import type { import type { ITelemetryEventsSender } from './sender'; import type { TelemetryEvent } from './types'; import type { ITelemetryReceiver } from './receiver'; +import { tlog } from './helpers'; /** * Preview telemetry events sender for the telemetry route. @@ -45,7 +46,8 @@ export class PreviewTelemetryEventsSender implements ITelemetryEventsSender { * Reject the request intentionally to stop from sending to the server */ this.axiosInstance.interceptors.request.use((config) => { - this.logger.debug( + tlog( + this.logger, `Intercepting telemetry', ${JSON.stringify( config.data )} and not sending data to the telemetry server` diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts index 0024228b23417..b517c7104bc26 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts @@ -39,6 +39,7 @@ import { trustedApplicationToTelemetryEntry, ruleExceptionListItemToTelemetryEvent, metricsResponseToValueListMetaData, + tlog, } from './helpers'; import { Fetcher } from '../../endpoint/routes/resolver/tree/utils/fetch'; import type { TreeOptions, TreeResponse } from '../../endpoint/routes/resolver/tree/utils/fetch'; @@ -667,7 +668,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { }; const response = await this.esClient.search(query, { meta: true }); - this.logger.debug(`received prebuilt alerts: (${response.body.hits.hits.length})`); + tlog(this.logger, `received prebuilt alerts: (${response.body.hits.hits.length})`); const telemetryEvents: TelemetryEvent[] = response.body.hits.hits.flatMap((h) => h._source != null ? ([h._source] as TelemetryEvent[]) : [] @@ -939,7 +940,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { return ret.license; } catch (err) { - this.logger.debug(`failed retrieving license: ${err}`); + tlog(this.logger, `failed retrieving license: ${err}`); return undefined; } } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts index 10ebe5f8679d6..52b91664a05a3 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts @@ -21,7 +21,7 @@ import type { import type { ITelemetryReceiver } from './receiver'; import { copyAllowlistedFields, endpointAllowlistFields } from './filterlists'; import { createTelemetryTaskConfigs } from './tasks'; -import { createUsageCounterLabel } from './helpers'; +import { createUsageCounterLabel, tlog } from './helpers'; import type { TelemetryEvent } from './types'; import { TELEMETRY_MAX_BUFFER_SIZE } from './constants'; import type { SecurityTelemetryTaskConfig } from './task'; @@ -87,7 +87,6 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { ) { this.telemetrySetup = telemetrySetup; this.telemetryUsageCounter = telemetryUsageCounter; - if (taskManager) { this.telemetryTasks = createTelemetryTaskConfigs().map( (config: SecurityTelemetryTaskConfig) => { @@ -114,13 +113,12 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { ) { this.telemetryStart = telemetryStart; this.receiver = receiver; - if (taskManager && this.telemetryTasks) { - this.logger.debug(`Starting security telemetry tasks`); + tlog(this.logger, `Starting security telemetry tasks`); this.telemetryTasks.forEach((task) => task.start(taskManager)); } - this.logger.debug(`Starting local task`); + tlog(this.logger, `Starting local task`); setTimeout(() => { this.sendIfDue(); this.intervalId = setInterval(() => this.sendIfDue(), this.checkIntervalMs); @@ -135,19 +133,21 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { public queueTelemetryEvents(events: TelemetryEvent[]) { const qlength = this.queue.length; - + tlog(this.logger, `Queue length is ${qlength}`); if (events.length === 0) { + tlog(this.logger, `No events to queue`); return; } - this.logger.debug(`Queue events`); + tlog(this.logger, `Queue ${events.length} events`); if (qlength >= this.maxQueueSize) { // we're full already + tlog(this.logger, `Queue length is greater than max queue size`); return; } - if (events.length > this.maxQueueSize - qlength) { + tlog(this.logger, `Events exceed remaining queue size ${this.maxQueueSize - qlength}`); this.telemetryUsageCounter?.incrementCounter({ counterName: createUsageCounterLabel(usageLabelPrefix.concat(['queue_stats'])), counterType: 'docs_lost', @@ -160,6 +160,7 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { }); this.queue.push(...this.processEvents(events.slice(0, this.maxQueueSize - qlength))); } else { + tlog(this.logger, `Events fit within queue size`); this.queue.push(...this.processEvents(events)); } } @@ -220,7 +221,7 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { const telemetryUrl = await this.fetchTelemetryPingUrl(); const resp = await axios.get(telemetryUrl, { timeout: 3000 }); if (resp.status === 200) { - this.logger.debug('[Security Telemetry] elastic telemetry services are reachable'); + tlog(this.logger, '[Security Telemetry] elastic telemetry services are reachable'); return true; } @@ -244,7 +245,7 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { this.isOptedIn = await this.isTelemetryOptedIn(); if (!this.isOptedIn) { - this.logger.debug(`Telemetry is not opted-in.`); + tlog(this.logger, `Telemetry is not opted-in.`); this.queue = []; this.isSending = false; return; @@ -252,7 +253,7 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { this.isElasticTelemetryReachable = await this.isTelemetryServicesReachable(); if (!this.isElasticTelemetryReachable) { - this.logger.debug(`Telemetry Services are not reachable.`); + tlog(this.logger, `Telemetry Services are not reachable.`); this.queue = []; this.isSending = false; return; @@ -265,8 +266,9 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { this.receiver?.fetchLicenseInfo(), ]); - this.logger.debug(`Telemetry URL: ${telemetryUrl}`); - this.logger.debug( + tlog(this.logger, `Telemetry URL: ${telemetryUrl}`); + tlog( + this.logger, `cluster_uuid: ${clusterInfo?.cluster_uuid} cluster_name: ${clusterInfo?.cluster_name}` ); @@ -321,8 +323,9 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { this.receiver?.fetchLicenseInfo(), ]); - this.logger.debug(`Telemetry URL: ${telemetryUrl}`); - this.logger.debug( + tlog(this.logger, `Telemetry URL: ${telemetryUrl}`); + tlog( + this.logger, `cluster_uuid: ${clusterInfo?.cluster_uuid} cluster_name: ${clusterInfo?.cluster_name}` ); @@ -385,7 +388,7 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { const ndjson = transformDataToNdjson(events); try { - this.logger.debug(`Sending ${events.length} telemetry events to ${channel}`); + tlog(this.logger, `Sending ${events.length} telemetry events to ${channel}`); const resp = await axiosInstance.post(telemetryUrl, ndjson, { headers: { 'Content-Type': 'application/x-ndjson', @@ -406,9 +409,9 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { counterType: 'docs_sent', incrementBy: events.length, }); - this.logger.debug(`Events sent!. Response: ${resp.status} ${JSON.stringify(resp.data)}`); + tlog(this.logger, `Events sent!. Response: ${resp.status} ${JSON.stringify(resp.data)}`); } catch (err) { - this.logger.debug(`Error sending events: ${err}`); + tlog(this.logger, `Error sending events: ${err}`); const errorStatus = err?.response?.status; if (errorStatus !== undefined && errorStatus !== null) { this.telemetryUsageCounter?.incrementCounter({ diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/task.ts b/x-pack/plugins/security_solution/server/lib/telemetry/task.ts index 11b9e5a28791c..e64f860125c7d 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/task.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/task.ts @@ -14,6 +14,7 @@ import type { } from '@kbn/task-manager-plugin/server'; import type { ITelemetryReceiver } from './receiver'; import type { ITelemetryEventsSender } from './sender'; +import { tlog } from './helpers'; export interface SecurityTelemetryTaskConfig { type: string; @@ -112,7 +113,7 @@ export class SecurityTelemetryTask { public start = async (taskManager: TaskManagerStartContract) => { const taskId = this.getTaskId(); - this.logger.debug(`[task ${taskId}]: attempting to schedule`); + tlog(this.logger, `[task ${taskId}]: attempting to schedule`); try { await taskManager.ensureScheduled({ id: taskId, @@ -130,25 +131,25 @@ export class SecurityTelemetryTask { }; public runTask = async (taskId: string, executionPeriod: TaskExecutionPeriod) => { - this.logger.debug(`[task ${taskId}]: attempting to run`); + tlog(this.logger, `[task ${taskId}]: attempting to run`); if (taskId !== this.getTaskId()) { - this.logger.debug(`[task ${taskId}]: outdated task`); + tlog(this.logger, `[task ${taskId}]: outdated task`); return 0; } const isOptedIn = await this.sender.isTelemetryOptedIn(); if (!isOptedIn) { - this.logger.debug(`[task ${taskId}]: telemetry is not opted-in`); + tlog(this.logger, `[task ${taskId}]: telemetry is not opted-in`); return 0; } const isTelemetryServicesReachable = await this.sender.isTelemetryServicesReachable(); if (!isTelemetryServicesReachable) { - this.logger.debug(`[task ${taskId}]: cannot reach telemetry services`); + tlog(this.logger, `[task ${taskId}]: cannot reach telemetry services`); return 0; } - this.logger.debug(`[task ${taskId}]: running task`); + tlog(this.logger, `[task ${taskId}]: running task`); return this.config.runTask(taskId, this.logger, this.receiver, this.sender, executionPeriod); }; } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts index 3ef093c86b082..1f22a4c97327d 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/detection_rule.ts @@ -7,7 +7,7 @@ import type { Logger } from '@kbn/core/server'; import { LIST_DETECTION_RULE_EXCEPTION, TELEMETRY_CHANNEL_LISTS } from '../constants'; -import { batchTelemetryRecords, templateExceptionList } from '../helpers'; +import { batchTelemetryRecords, templateExceptionList, tlog } from '../helpers'; import type { ITelemetryEventsSender } from '../sender'; import type { ITelemetryReceiver } from '../receiver'; import type { ExceptionListItem, ESClusterInfo, ESLicense, RuleSearchResult } from '../types'; @@ -27,6 +27,7 @@ export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: n sender: ITelemetryEventsSender, taskExecutionPeriod: TaskExecutionPeriod ) => { + tlog(logger, 'test'); const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([ receiver.fetchClusterInfo(), receiver.fetchLicenseInfo(), @@ -46,7 +47,7 @@ export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: n const { body: prebuiltRules } = await receiver.fetchDetectionRules(); if (!prebuiltRules) { - logger.debug('no prebuilt rules found'); + tlog(logger, 'no prebuilt rules found'); return 0; } @@ -87,7 +88,7 @@ export function createTelemetryDetectionRuleListsTaskConfig(maxTelemetryBatch: n licenseInfo, LIST_DETECTION_RULE_EXCEPTION ); - + tlog(logger, `Detection rule exception json length ${detectionRuleExceptionsJson.length}`); const batches = batchTelemetryRecords(detectionRuleExceptionsJson, maxTelemetryBatch); for (const batch of batches) { await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, batch); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts index b66f502d08531..579e0e6cf9675 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/diagnostic.ts @@ -6,7 +6,7 @@ */ import type { Logger } from '@kbn/core/server'; -import { getPreviousDiagTaskTimestamp } from '../helpers'; +import { tlog, getPreviousDiagTaskTimestamp } from '../helpers'; import type { ITelemetryEventsSender } from '../sender'; import type { TelemetryEvent } from '../types'; import type { ITelemetryReceiver } from '../receiver'; @@ -38,11 +38,10 @@ export function createTelemetryDiagnosticsTaskConfig() { const hits = response.hits?.hits || []; if (!Array.isArray(hits) || !hits.length) { - logger.debug('no diagnostic alerts retrieved'); + tlog(logger, 'no diagnostic alerts retrieved'); return 0; } - logger.debug(`Received ${hits.length} diagnostic alerts`); - + tlog(logger, `Received ${hits.length} diagnostic alerts`); const diagAlerts: TelemetryEvent[] = hits.flatMap((h) => h._source != null ? [h._source] : [] ); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts index 2ad99d6656678..c3c1cdf54e4d9 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts @@ -26,6 +26,7 @@ import { extractEndpointPolicyConfig, getPreviousDailyTaskTimestamp, isPackagePolicyList, + tlog, } from '../helpers'; import type { PolicyData } from '../../../../common/endpoint/types'; import { TELEMETRY_CHANNEL_ENDPOINT_META } from '../constants'; @@ -57,10 +58,10 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { sender: ITelemetryEventsSender, taskExecutionPeriod: TaskExecutionPeriod ) => { + tlog(logger, 'test'); if (!taskExecutionPeriod.last) { throw new Error('last execution timestamp is required'); } - const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([ receiver.fetchClusterInfo(), receiver.fetchLicenseInfo(), @@ -89,7 +90,7 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { * a metric document(s) exists for an EP agent we map to fleet agent and policy */ if (endpointData.endpointMetrics === undefined) { - logger.debug(`no endpoint metrics to report`); + tlog(logger, `no endpoint metrics to report`); return 0; } @@ -98,7 +99,7 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { }; if (endpointMetricsResponse.aggregations === undefined) { - logger.debug(`no endpoint metrics to report`); + tlog(logger, `no endpoint metrics to report`); return 0; } @@ -131,7 +132,7 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { const agentsResponse = endpointData.fleetAgentsResponse; if (agentsResponse === undefined) { - logger.debug('no fleet agent information available'); + tlog(logger, 'no fleet agent information available'); return 0; } @@ -154,10 +155,12 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { policyInfo !== undefined && !endpointPolicyCache.has(policyInfo) ) { + tlog(logger, `policy info exists as ${policyInfo}`); const agentPolicy = await receiver.fetchPolicyConfigs(policyInfo); const packagePolicies = agentPolicy?.package_policies; if (packagePolicies !== undefined && isPackagePolicyList(packagePolicies)) { + tlog(logger, `package policy exists as ${JSON.stringify(packagePolicies)}`); packagePolicies .map((pPolicy) => pPolicy as PolicyData) .forEach((pPolicy) => { @@ -203,6 +206,11 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { ) : new Map(); + tlog( + logger, + `policy responses exists as ${JSON.stringify(Object.fromEntries(policyResponses))}` + ); + /** STAGE 4 - Fetch Endpoint Agent Metadata * * Reads Endpoint Agent metadata out of the `.ds-metrics-endpoint.metadata` data stream @@ -211,7 +219,7 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { * a metadata document(s) exists for an EP agent we map to fleet agent and policy */ if (endpointData.endpointMetadata === undefined) { - logger.debug(`no endpoint metadata to report`); + tlog(logger, `no endpoint metadata to report`); } const { body: endpointMetadataResponse } = endpointData.endpointMetadata as unknown as { @@ -219,7 +227,7 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { }; if (endpointMetadataResponse.aggregations === undefined) { - logger.debug(`no endpoint metadata to report`); + tlog(logger, `no endpoint metadata to report`); } const endpointMetadata = @@ -231,7 +239,10 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { }, new Map() ); - + tlog( + logger, + `endpoint metadata exists as ${JSON.stringify(Object.fromEntries(endpointMetadata))}` + ); /** STAGE 5 - Create the telemetry log records * * Iterates through the endpoint metrics documents at STAGE 1 and joins them together @@ -334,7 +345,7 @@ export function createTelemetryEndpointTaskConfig(maxTelemetryBatch: number) { } return telemetryPayloads.length; } catch (err) { - logger.warn('could not complete endpoint alert telemetry task'); + logger.warn(`could not complete endpoint alert telemetry task due to ${err?.message}`); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts index 081bf82a944e3..44a6b3cf644f4 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/prebuilt_rule_alerts.ts @@ -11,7 +11,7 @@ import type { ITelemetryReceiver } from '../receiver'; import type { ESClusterInfo, ESLicense, TelemetryEvent } from '../types'; import type { TaskExecutionPeriod } from '../task'; import { TELEMETRY_CHANNEL_DETECTION_ALERTS } from '../constants'; -import { batchTelemetryRecords } from '../helpers'; +import { batchTelemetryRecords, tlog } from '../helpers'; import { copyAllowlistedFields, prebuiltRuleAllowlistFields } from '../filterlists'; export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: number) { @@ -53,7 +53,7 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n }); if (telemetryEvents.length === 0) { - logger.debug('no prebuilt rule alerts retrieved'); + tlog(logger, 'no prebuilt rule alerts retrieved'); return 0; } @@ -71,7 +71,7 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n }) ); - logger.debug(`sending ${enrichedAlerts.length} elastic prebuilt alerts`); + tlog(logger, `sending ${enrichedAlerts.length} elastic prebuilt alerts`); const batches = batchTelemetryRecords(enrichedAlerts, maxTelemetryBatch); for (const batch of batches) { await sender.sendOnDemand(TELEMETRY_CHANNEL_DETECTION_ALERTS, batch); @@ -79,7 +79,7 @@ export function createTelemetryPrebuiltRuleAlertsTaskConfig(maxTelemetryBatch: n return enrichedAlerts.length; } catch (err) { - logger.debug('could not complete prebuilt alerts telemetry task'); + logger.error('could not complete prebuilt alerts telemetry task'); return 0; } }, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts index 1788eba66e15a..a6023d809c6b0 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts @@ -17,7 +17,7 @@ import { TELEMETRY_CHANNEL_LISTS, } from '../constants'; import type { ESClusterInfo, ESLicense } from '../types'; -import { batchTelemetryRecords, templateExceptionList } from '../helpers'; +import { batchTelemetryRecords, templateExceptionList, tlog } from '../helpers'; import type { ITelemetryEventsSender } from '../sender'; import type { ITelemetryReceiver } from '../receiver'; import type { TaskExecutionPeriod } from '../task'; @@ -62,7 +62,7 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number) licenseInfo, LIST_TRUSTED_APPLICATION ); - logger.debug(`Trusted Apps: ${trustedAppsJson}`); + tlog(logger, `Trusted Apps: ${trustedAppsJson}`); count += trustedAppsJson.length; const batches = batchTelemetryRecords(trustedAppsJson, maxTelemetryBatch); @@ -81,7 +81,7 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number) licenseInfo, LIST_ENDPOINT_EXCEPTION ); - logger.debug(`EP Exceptions: ${epExceptionsJson}`); + tlog(logger, `EP Exceptions: ${epExceptionsJson}`); count += epExceptionsJson.length; const batches = batchTelemetryRecords(epExceptionsJson, maxTelemetryBatch); @@ -100,7 +100,7 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number) licenseInfo, LIST_ENDPOINT_EVENT_FILTER ); - logger.debug(`EP Event Filters: ${epFiltersJson}`); + tlog(logger, `EP Event Filters: ${epFiltersJson}`); count += epFiltersJson.length; const batches = batchTelemetryRecords(epFiltersJson, maxTelemetryBatch); @@ -113,6 +113,7 @@ export function createTelemetrySecurityListTaskConfig(maxTelemetryBatch: number) const valueListMetaData = await receiver.fetchValueListMetaData( FETCH_VALUE_LIST_META_DATA_INTERVAL_IN_HOURS ); + tlog(logger, `Value List Meta Data: ${JSON.stringify(valueListMetaData)}`); if (valueListMetaData?.total_list_count) { await sender.sendOnDemand(TELEMETRY_CHANNEL_LISTS, [valueListMetaData]); } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts index c514eb6428de4..8403bbd7f30fd 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/timelines.ts @@ -19,6 +19,7 @@ import type { } from '../types'; import { TELEMETRY_CHANNEL_TIMELINE } from '../constants'; import { resolverEntity } from '../../../endpoint/routes/resolver/entity/utils/build_resolver_entity'; +import { tlog } from '../helpers'; export function createTelemetryTimelineTaskConfig() { return { @@ -36,7 +37,7 @@ export function createTelemetryTimelineTaskConfig() { ) => { let counter = 0; - logger.debug(`Running task: ${taskId}`); + tlog(logger, `Running task: ${taskId}`); const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([ receiver.fetchClusterInfo(), @@ -71,6 +72,7 @@ export function createTelemetryTimelineTaskConfig() { const aggregations = endpointAlerts?.aggregations as unknown as { endpoint_alert_count: { value: number }; }; + tlog(logger, `Endpoint alert count: ${aggregations?.endpoint_alert_count}`); sender.getTelemetryUsageCluster()?.incrementCounter({ counterName: 'telemetry_endpoint_alert', counterType: 'endpoint_alert_count', @@ -82,7 +84,7 @@ export function createTelemetryTimelineTaskConfig() { endpointAlerts.hits.hits?.length === 0 || endpointAlerts.hits.hits?.length === undefined ) { - logger.debug('no endpoint alerts received. exiting telemetry task.'); + tlog(logger, 'no endpoint alerts received. exiting telemetry task.'); return counter; } @@ -120,7 +122,7 @@ export function createTelemetryTimelineTaskConfig() { // Fetch event lineage const timelineEvents = await receiver.fetchTimelineEvents(nodeIds); - + tlog(logger, `Timeline Events: ${JSON.stringify(timelineEvents)}`); const eventsStore = new Map(); for (const event of timelineEvents.hits.hits) { const doc = event._source; @@ -166,11 +168,11 @@ export function createTelemetryTimelineTaskConfig() { sender.sendOnDemand(TELEMETRY_CHANNEL_TIMELINE, [record]); counter += 1; } else { - logger.debug('no events in timeline'); + tlog(logger, 'no events in timeline'); } } - logger.debug(`sent ${counter} timelines. concluding timeline task.`); + tlog(logger, `sent ${counter} timelines. concluding timeline task.`); return counter; }, }; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 5360de15d715c..1fa0a3ae603ee 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -97,6 +97,7 @@ import type { import { alertsFieldMap, rulesFieldMap } from '../common/field_maps'; import { EndpointFleetServicesFactory } from './endpoint/services/fleet'; import { featureUsageService } from './endpoint/services/feature_usage'; +import { setIsElasticCloudDeployment } from './lib/telemetry/helpers'; export type { SetupPlugins, StartPlugins, PluginSetup, PluginStart } from './plugin_contract'; @@ -331,6 +332,8 @@ export class Plugin implements ISecuritySolutionPlugin { ); }); + setIsElasticCloudDeployment(plugins.cloud.isCloudEnabled ?? false); + this.telemetryEventsSender.setup( this.telemetryReceiver, plugins.telemetry, diff --git a/x-pack/plugins/security_solution/server/plugin_contract.ts b/x-pack/plugins/security_solution/server/plugin_contract.ts index 4abf6516696aa..87c374de92763 100644 --- a/x-pack/plugins/security_solution/server/plugin_contract.ts +++ b/x-pack/plugins/security_solution/server/plugin_contract.ts @@ -34,9 +34,11 @@ import type { TaskManagerStartContract as TaskManagerPluginStart, } from '@kbn/task-manager-plugin/server'; import type { TelemetryPluginStart, TelemetryPluginSetup } from '@kbn/telemetry-plugin/server'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; export interface SecuritySolutionPluginSetupDependencies { alerting: AlertingPluginSetup; + cloud: CloudSetup; data: DataPluginSetup; encryptedSavedObjects?: EncryptedSavedObjectsPluginSetup; eventLog: IEventLogService; diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index af14b3c01226d..5462da4f07710 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -8,6 +8,7 @@ import type { StartServicesAccessor, Logger } from '@kbn/core/server'; import type { IRuleDataClient, RuleDataPluginService } from '@kbn/rule-registry-plugin/server'; +import { getRiskScoreIndexStatusRoute } from '../lib/risk_score/routes'; import type { SecuritySolutionPluginRouter } from '../types'; import { createRulesRoute } from '../lib/detection_engine/routes/rules/create_rules_route'; @@ -184,4 +185,6 @@ export const initRoutes = ( // telemetry preview endpoint for e2e integration tests only at the moment. telemetryDetectionRulesPreviewRoute(router, logger, previewTelemetryReceiver, telemetrySender); } + + getRiskScoreIndexStatusRoute(router); }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts index d21bc53de178f..4208ea44937bc 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts @@ -28,15 +28,11 @@ class IndexNotFoundException extends Error { } } -const mockDeps = (riskyHostsEnabled = true) => ({ +const mockDeps = () => ({ ...defaultMockDeps, spaceId: 'test-space', endpointContext: { ...defaultMockDeps.endpointContext, - experimentalFeatures: { - ...defaultMockDeps.endpointContext.experimentalFeatures, - riskyHostsEnabled, - }, }, }); @@ -70,7 +66,11 @@ describe('allHosts search strategy', () => { describe('parse', () => { test('should parse data correctly', async () => { - const result = await allHosts.parse(mockOptions, mockSearchStrategyResponse, mockDeps(false)); + const mockedDeps = mockDeps(); + // @ts-expect-error incomplete type + mockedDeps.esClient.asCurrentUser.search.mockResponse({ hits: { hits: [] } }); + + const result = await allHosts.parse(mockOptions, mockSearchStrategyResponse, mockDeps()); expect(result).toMatchObject(formattedSearchStrategyResponse); }); @@ -131,35 +131,6 @@ describe('allHosts search strategy', () => { }); }); - test('should not enhance data when feature flag is disabled', async () => { - const risk = 'TEST_RISK_SCORE'; - const hostName: string = get( - 'aggregations.host_data.buckets[0].key', - mockSearchStrategyResponse.rawResponse - ); - const mockedDeps = mockDeps(false); - - mockedDeps.esClient.asCurrentUser.search.mockResponse({ - hits: { - hits: [ - // @ts-expect-error incomplete type - { - _source: { - risk, - host: { - name: hostName, - }, - }, - }, - ], - }, - }); - - const result = await allHosts.parse(mockOptions, mockSearchStrategyResponse, mockedDeps); - - expect(result.edges[0].node.risk).toBeUndefined(); - }); - test("should not enhance data when index doesn't exist", async () => { const mockedDeps = mockDeps(); mockedDeps.esClient.asCurrentUser.search.mockImplementation(() => { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts index cecfc60fbbaed..fb4a4aa27353b 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts @@ -62,10 +62,9 @@ export const allHosts: SecuritySolutionFactory = { const hostNames = edges.map((edge) => getOr('', 'node.host.name[0]', edge)); - const enhancedEdges = - deps?.spaceId && deps?.endpointContext.experimentalFeatures.riskyHostsEnabled - ? await enhanceEdges(edges, hostNames, deps.spaceId, deps.esClient) - : edges; + const enhancedEdges = deps?.spaceId + ? await enhanceEdges(edges, hostNames, deps.spaceId, deps.esClient) + : edges; return { ...response, @@ -88,11 +87,10 @@ async function enhanceEdges( esClient: IScopedClusterClient ): Promise { const hostRiskData = await getHostRiskData(esClient, spaceId, hostNames); - const hostsRiskByHostName: Record | undefined = hostRiskData?.hits.hits.reduce( (acc, hit) => ({ ...acc, - [hit._source?.host.name ?? '']: hit._source?.host.risk.calculated_level, + [hit._source?.host.name ?? '']: hit._source?.host?.risk?.calculated_level, }), {} ); diff --git a/x-pack/plugins/session_view/common/types/process_tree/index.ts b/x-pack/plugins/session_view/common/types/process_tree/index.ts index a2c825cae1433..68f8924abd702 100644 --- a/x-pack/plugins/session_view/common/types/process_tree/index.ts +++ b/x-pack/plugins/session_view/common/types/process_tree/index.ts @@ -76,6 +76,7 @@ export interface ProcessStartMarker { export interface IOFields { text?: string; + max_bytes_per_process_exceeded?: boolean; } export interface ProcessFields { diff --git a/x-pack/plugins/session_view/public/components/session_view/hooks.ts b/x-pack/plugins/session_view/public/components/session_view/hooks.ts index 50bc64f885bde..7d33b5bfbe957 100644 --- a/x-pack/plugins/session_view/public/components/session_view/hooks.ts +++ b/x-pack/plugins/session_view/public/components/session_view/hooks.ts @@ -64,10 +64,12 @@ export const useFetchSessionViewProcessEvents = ( getNextPageParam: (lastPage, pages) => { const isRefetch = pages.length === 1 && jumpToCursor; if (isRefetch || lastPage.events.length >= PROCESS_EVENTS_PER_PAGE) { - const cursor = lastPage.events.filter((event) => { + const filtered = lastPage.events.filter((event) => { const action = event.event?.action; return action && [EventAction.fork, EventAction.exec, EventAction.end].includes(action); - })?.[lastPage.events.length - 1]?.['@timestamp']; + }); + + const cursor = filtered?.[filtered.length - 1]?.['@timestamp']; if (cursor) { return { @@ -78,12 +80,17 @@ export const useFetchSessionViewProcessEvents = ( } }, getPreviousPageParam: (firstPage, pages) => { - const atBeginning = pages.length > 1 && firstPage.events.length < PROCESS_EVENTS_PER_PAGE; + const filtered = firstPage.events.filter((event) => { + const action = event.event?.action; + return action && [EventAction.fork, EventAction.exec, EventAction.end].includes(action); + }); + + const atBeginning = pages.length > 1 && filtered.length < PROCESS_EVENTS_PER_PAGE; if (jumpToCursor && !atBeginning) { // it's possible the first page returned no events // fallback to using jumpToCursor if there are no "forward" events. - const cursor = firstPage.events?.[0]?.['@timestamp'] || jumpToCursor; + const cursor = filtered[0]?.['@timestamp'] || jumpToCursor; return { cursor, diff --git a/x-pack/plugins/session_view/public/components/tty_player_controls/index.test.tsx b/x-pack/plugins/session_view/public/components/tty_player_controls/index.test.tsx index 9417e72186db0..a98cb544b9068 100644 --- a/x-pack/plugins/session_view/public/components/tty_player_controls/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player_controls/index.test.tsx @@ -99,4 +99,43 @@ describe('TTYPlayerControls component', () => { renderResult.queryByTestId('sessionView:TTYPlayerControlsEnd')?.click(); expect(props.onSeekLine).toHaveBeenCalledWith(10); }); + + it('render output markers', async () => { + renderResult = mockedContext.render(); + expect( + renderResult.queryAllByRole('button', { + name: 'output', + }) + ).toHaveLength(props.processStartMarkers.length); + }); + it('render data_limited markers', async () => { + const processStartMarkers = [ + { event: MOCK_PROCESS_EVENT_START, line: 0 }, + { + event: { + process: { + ...MOCK_PROCESS_EVENT_MIDDLE, + io: { + max_bytes_per_process_exceeded: true, + }, + }, + }, + line: 2, + }, + { event: MOCK_PROCESS_EVENT_END, line: 4 }, + ]; + renderResult = mockedContext.render( + + ); + expect( + renderResult.queryAllByRole('button', { + name: 'output', + }) + ).toHaveLength(2); + expect( + renderResult.queryAllByRole('button', { + name: 'data_limited', + }) + ).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/session_view/public/components/tty_player_controls/index.tsx b/x-pack/plugins/session_view/public/components/tty_player_controls/index.tsx index a298474b77f6b..fffb7493f4c9e 100644 --- a/x-pack/plugins/session_view/public/components/tty_player_controls/index.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player_controls/index.tsx @@ -8,7 +8,6 @@ import React, { useCallback, ChangeEvent, MouseEvent } from 'react'; import { EuiButtonEmpty, EuiPanel, - EuiRange, EuiFlexGroup, EuiFlexItem, EuiButtonIcon, @@ -26,6 +25,7 @@ import { TTY_START, VIEW_IN_SESSION, } from './translations'; +import { TTYPlayerControlsMarkers } from './tty_player_controls_markers'; export interface TTYPlayerControlsDeps { currentProcessEvent: ProcessEvent | undefined; @@ -71,19 +71,12 @@ export const TTYPlayerControls = ({ }, [linesLength, onSeekLine]); const seekToPrevProcess = useCallback(() => { - let index = findIndex(processStartMarkers, (marker) => { - if (marker.line > currentLine) { - return true; - } - - return false; - }); - - if (index === -1) { - index = processStartMarkers.length - 1; - } + const index = + currentLine > processStartMarkers[processStartMarkers.length - 1].line + ? processStartMarkers.length + : findIndex(processStartMarkers, (marker) => marker.line >= currentLine); - const previousMarker = processStartMarkers[index - 2]; + const previousMarker = processStartMarkers[index - 1]; onSeekLine(previousMarker?.line || 0); }, [processStartMarkers, onSeekLine, currentLine]); @@ -97,7 +90,7 @@ export const TTYPlayerControls = ({ }); const nextMarker = processStartMarkers[nextIndex]; - onSeekLine(nextMarker?.line || linesLength); + onSeekLine(nextMarker?.line || linesLength - 1); }, [processStartMarkers, onSeekLine, linesLength, currentLine]); const handleViewInSession = useCallback(() => { @@ -180,13 +173,12 @@ export const TTYPlayerControls = ({ /> - - + diff --git a/x-pack/plugins/session_view/public/components/tty_player_controls/styles.ts b/x-pack/plugins/session_view/public/components/tty_player_controls/styles.ts index 60e4e3c9b71cd..e3a7b899625da 100644 --- a/x-pack/plugins/session_view/public/components/tty_player_controls/styles.ts +++ b/x-pack/plugins/session_view/public/components/tty_player_controls/styles.ts @@ -10,7 +10,7 @@ import { CSSObject } from '@emotion/react'; import { useEuiTheme } from '../../hooks'; export const useStyles = () => { - const { euiTheme } = useEuiTheme(); + const { euiTheme, euiVars } = useEuiTheme(); const cached = useMemo(() => { const { size, border } = euiTheme; @@ -19,6 +19,7 @@ export const useStyles = () => { paddingBottom: size.s, borderBottomLeftRadius: border.radius.medium, borderBottomRightRadius: border.radius.medium, + backgroundColor: euiVars.terminalOutputBackground, }; const controlButton: CSSObject = { @@ -29,7 +30,7 @@ export const useStyles = () => { controlsPanel, controlButton, }; - }, [euiTheme]); + }, [euiTheme, euiVars.terminalOutputBackground]); return cached; }; diff --git a/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/index.tsx b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/index.tsx new file mode 100644 index 0000000000000..2eb0c0b3ecf88 --- /dev/null +++ b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/index.tsx @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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, { ChangeEvent, MouseEvent, useMemo } from 'react'; +import { EuiRange } from '@elastic/eui'; +import type { ProcessStartMarker } from '../../../../common/types/process_tree'; +import { useStyles } from './styles'; +import { PlayHead } from './play_head'; + +type Props = { + processStartMarkers: ProcessStartMarker[]; + linesLength: number; + currentLine: number; + onChange: (e: ChangeEvent | MouseEvent) => void; +}; + +type TTYPlayerLineMarker = { + line: number; + type: 'output' | 'data_limited'; +}; + +export const TTYPlayerControlsMarkers = ({ + processStartMarkers, + linesLength, + currentLine, + onChange, +}: Props) => { + const styles = useStyles(); + + const markers = useMemo(() => { + if (processStartMarkers.length < 1) { + return []; + } + return processStartMarkers.map( + ({ event, line }) => + ({ + type: + event.process?.io?.max_bytes_per_process_exceeded === true ? 'data_limited' : 'output', + line, + } as TTYPlayerLineMarker) + ); + }, [processStartMarkers]); + + const markersLength = markers.length; + if (!markersLength) { + return null; + } + + const currentSelected = + currentLine >= markers[markersLength - 1].line + ? markersLength - 1 + : markers.findIndex((marker) => marker.line > currentLine) - 1; + + const currentSelectedType = markers[Math.max(0, currentSelected)].type; + + return ( + <> + + +
+ {markers.map(({ line, type }, idx) => { + const selected = + currentLine >= line && + (idx === markersLength - 1 || currentLine < markers[idx + 1].line); + + // markers positions are absolute, setting higher z-index on the selected one in case there + // are severals next to each other + const style = { + left: `${(line * 100) / linesLength}%`, + zIndex: selected ? 3 : 2, + }; + + return ( + + ); + })} +
+ + ); +}; diff --git a/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/play_head.tsx b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/play_head.tsx new file mode 100644 index 0000000000000..6cfd5cb70e62d --- /dev/null +++ b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/play_head.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 * as React from 'react'; + +interface SVGRProps { + title?: string; + titleId?: string; +} + +/** + * Custom component for replacing the thumb of an input of type range + */ +export const PlayHead = ({ + title, + titleId, + ...props +}: React.SVGProps & SVGRProps) => ( + + {title ? {title} : null} + + +); diff --git a/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts new file mode 100644 index 0000000000000..03134947d8f7a --- /dev/null +++ b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { CSSObject } from '@emotion/react'; +import { useEuiTheme } from '../../../hooks'; + +type TTYPlayerLineMarkerType = 'output' | 'data_limited'; + +export const useStyles = () => { + const { euiTheme, euiVars } = useEuiTheme(); + const cached = useMemo(() => { + const { border } = euiTheme; + + const markersOverlay: CSSObject = { + top: 5, + zIndex: 2, + position: 'absolute', + width: '100%', + }; + + const getMarkerBackgroundColor = (type: TTYPlayerLineMarkerType, selected: boolean) => { + if (type === 'data_limited') { + return euiVars.terminalOutputMarkerWarning; + } + if (selected) { + return euiVars.terminalOutputMarkerAccent; + } + return euiVars.euiColorVis1; + }; + + const marker = (type: TTYPlayerLineMarkerType, selected: boolean): CSSObject => ({ + fontSize: 0, + overflow: 'hidden', + position: 'absolute', + padding: 0, + width: 3, + height: 12, + backgroundColor: getMarkerBackgroundColor(type, selected), + border: `${border.width.thick} solid ${euiVars.terminalOutputBackground}`, + borderRadius: border.radius.small, + boxSizing: 'content-box', + top: 0, + pointerEvents: 'none', + marginLeft: '-3.5px', + }); + + const playHeadThumb: CSSObject = { + cursor: 'pointer', + marginTop: -20, + width: 9, + height: 30, + marginLeft: -4.5, + }; + + const customThumb: CSSObject = { + ...playHeadThumb, + border: 'none', + boxShadow: 'none', + backgroundColor: 'transparent', + borderRadius: 0, + opacity: 0, + appearance: 'none', + }; + + // Custom css for input type range, overrinding some options of EuiRange + // The custom thumb below is visually hidden and shares the same css properties with the playHead component + // Source (Chrome): https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb + // Source (Firefox): https://developer.mozilla.org/en-US/docs/Web/CSS/::-moz-range-thumb + const range: CSSObject = { + height: 25, + '&:focus:not(:focus-visible)::-webkit-slider-thumb': { + boxShadow: 'none', + backgroundColor: 'transparent', + }, + "input[type='range']::-webkit-slider-thumb": customThumb, + "input[type='range']::-moz-range-thumb": customThumb, + '.euiRangeHighlight__progress': { + backgroundColor: euiVars.euiColorVis0_behindText, + }, + '.euiRangeSlider:focus ~ .euiRangeHighlight .euiRangeHighlight__progress': { + backgroundColor: euiVars.euiColorVis0_behindText, + }, + '.euiRangeSlider:focus:not(:focus-visible) ~ .euiRangeHighlight .euiRangeHighlight__progress': + { backgroundColor: euiVars.euiColorVis0_behindText }, + '.euiRangeTrack::after': { + background: euiVars.terminalOutputSliderBackground, + }, + }; + + const playHead = (type: TTYPlayerLineMarkerType): CSSObject => ({ + ...playHeadThumb, + position: 'absolute', + top: 16, + fill: + type === 'data_limited' + ? euiVars.terminalOutputMarkerWarning + : euiVars.terminalOutputMarkerAccent, + }); + + return { + marker, + markersOverlay, + range, + playHead, + }; + }, [euiTheme, euiVars]); + + return cached; +}; diff --git a/x-pack/plugins/session_view/public/hooks/use_eui_theme.ts b/x-pack/plugins/session_view/public/hooks/use_eui_theme.ts index 02f7dd479d2ac..a62cab176657b 100644 --- a/x-pack/plugins/session_view/public/hooks/use_eui_theme.ts +++ b/x-pack/plugins/session_view/public/hooks/use_eui_theme.ts @@ -14,6 +14,10 @@ type ExtraEuiVars = { // eslint-disable-next-line @typescript-eslint/naming-convention euiColorVis6_asText: string; buttonsBackgroundNormalDefaultPrimary: string; + terminalOutputBackground: string; + terminalOutputMarkerAccent: string; + terminalOutputMarkerWarning: string; + terminalOutputSliderBackground: string; }; type EuiVars = typeof euiLightVars & ExtraEuiVars; type EuiThemeReturn = ReturnType & { euiVars: EuiVars }; @@ -31,6 +35,11 @@ export const useEuiTheme = (...props: EuiThemeProps): EuiThemeReturn => { // eslint-disable-next-line @typescript-eslint/naming-convention euiColorVis6_asText: shade(themeVars.euiColorVis6, 0.335), buttonsBackgroundNormalDefaultPrimary: '#006DE4', + // Terminal Output Colors don't change with the theme + terminalOutputBackground: '#1d1e23', + terminalOutputMarkerAccent: euiLightVars.euiColorAccent, + terminalOutputMarkerWarning: euiDarkVars.euiColorWarning, + terminalOutputSliderBackground: euiLightVars.euiColorDarkestShade, }; return { diff --git a/x-pack/plugins/stack_connectors/README.md b/x-pack/plugins/stack_connectors/README.md new file mode 100644 index 0000000000000..38685950658d6 --- /dev/null +++ b/x-pack/plugins/stack_connectors/README.md @@ -0,0 +1,373 @@ +# Stack Connectors + +The `stack_connectors` plugin provides connector types shipped with Kibana, built on top of the framework provided in the `actions` plugin. + +--- + +Table of Contents + +- [Connector Types](#connector-types) + - [ServiceNow ITSM](#servicenow-itsm) + - [`params`](#params) + - [`subActionParams (pushToService)`](#subactionparams-pushtoservice) + - [`subActionParams (getFields)`](#subactionparams-getfields) + - [`subActionParams (getIncident)`](#subactionparams-getincident) + - [`subActionParams (getChoices)`](#subactionparams-getchoices) + - [ServiceNow Sec Ops](#servicenow-sec-ops) + - [`params`](#params-1) + - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-1) + - [`subActionParams (getFields)`](#subactionparams-getfields-1) + - [`subActionParams (getIncident)`](#subactionparams-getincident-1) + - [`subActionParams (getChoices)`](#subactionparams-getchoices-1) + - [ServiceNow ITOM](#servicenow-itom) + - [`params`](#params-2) + - [`subActionParams (addEvent)`](#subactionparams-addevent) + - [`subActionParams (getChoices)`](#subactionparams-getchoices-2) + - [Jira](#jira) + - [`params`](#params-3) + - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-2) + - [`subActionParams (getIncident)`](#subactionparams-getincident-2) + - [`subActionParams (issueTypes)`](#subactionparams-issuetypes) + - [`subActionParams (fieldsByIssueType)`](#subactionparams-fieldsbyissuetype) + - [`subActionParams (issues)`](#subactionparams-issues) + - [`subActionParams (issue)`](#subactionparams-issue) + - [`subActionParams (getFields)`](#subactionparams-getfields-2) + - [IBM Resilient](#ibm-resilient) + - [`params`](#params-4) + - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-3) + - [`subActionParams (getFields)`](#subactionparams-getfields-3) + - [`subActionParams (incidentTypes)`](#subactionparams-incidenttypes) + - [`subActionParams (severity)`](#subactionparams-severity) + - [Swimlane](#swimlane) + - [`params`](#params-5) + - [| severity | The severity of the incident. | string _(optional)_ |](#-severity-----the-severity-of-the-incident-----string-optional-) +- [Developing New Connector Types](#developing-new-connector-types) + - [licensing](#licensing) + - [plugin location](#plugin-location) + - [documentation](#documentation) + - [tests](#tests) + - [connector type config and secrets](#connector-type-config-and-secrets) + - [user interface](#user-interface) + +# Connector Types + +Kibana ships with a set of built-in connector types. See [Connectors Documentation](https://www.elastic.co/guide/en/kibana/master/action-types.html). + +In addition to the documented configurations, several built in connector type offer additional `params` configurations. + +## ServiceNow ITSM + +The [ServiceNow ITSM user documentation `params`](https://www.elastic.co/guide/en/kibana/master/servicenow-action-type.html) lists configuration properties for the `pushToService` subaction. In addition, several other subaction types are available. +### `params` + +| Property | Description | Type | +| --------------- | -------------------------------------------------------------------------------------------------- | ------ | +| subAction | The subaction to perform. It can be `pushToService`, `getFields`, `getIncident`, and `getChoices`. | string | +| subActionParams | The parameters of the subaction. | object | + +#### `subActionParams (pushToService)` + +| Property | Description | Type | +| -------- | ------------------------------------------------------------------------------------------------------------- | --------------------- | +| incident | The ServiceNow incident. | object | +| comments | The comments of the case. A comment is of the form `{ commentId: string, version: string, comment: string }`. | object[] _(optional)_ | + +The following table describes the properties of the `incident` object. + +| Property | Description | Type | +| ------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------- | +| short_description | The title of the incident. | string | +| description | The description of the incident. | string _(optional)_ | +| externalId | The ID of the incident in ServiceNow. If present, the incident is updated. Otherwise, a new incident is created. | string _(optional)_ | +| severity | The severity in ServiceNow. | string _(optional)_ | +| urgency | The urgency in ServiceNow. | string _(optional)_ | +| impact | The impact in ServiceNow. | string _(optional)_ | +| category | The category in ServiceNow. | string _(optional)_ | +| subcategory | The subcategory in ServiceNow. | string _(optional)_ | +| correlation_id | The correlation id of the incident. | string _(optional)_ | +| correlation_display | The correlation display of the ServiceNow. | string _(optional)_ | + +#### `subActionParams (getFields)` + +No parameters for the `getFields` subaction. Provide an empty object `{}`. + +#### `subActionParams (getIncident)` + +| Property | Description | Type | +| ---------- | ------------------------------------- | ------ | +| externalId | The ID of the incident in ServiceNow. | string | + + +#### `subActionParams (getChoices)` + +| Property | Description | Type | +| -------- | -------------------------------------------------- | -------- | +| fields | An array of fields. Example: `[category, impact]`. | string[] | + +--- + +## ServiceNow Sec Ops + +The [ServiceNow SecOps user documentation `params`](https://www.elastic.co/guide/en/kibana/master/servicenow-sir-action-type.html) lists configuration properties for the `pushToService` subaction. In addition, several other subaction types are available. + +### `params` + +| Property | Description | Type | +| --------------- | -------------------------------------------------------------------------------------------------- | ------ | +| subAction | The subaction to perform. It can be `pushToService`, `getFields`, `getIncident`, and `getChoices`. | string | +| subActionParams | The parameters of the subaction. | object | + +#### `subActionParams (pushToService)` + +| Property | Description | Type | +| -------- | ------------------------------------------------------------------------------------------------------------- | --------------------- | +| incident | The ServiceNow security incident. | object | +| comments | The comments of the case. A comment is of the form `{ commentId: string, version: string, comment: string }`. | object[] _(optional)_ | + +The following table describes the properties of the `incident` object. + +| Property | Description | Type | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | +| short_description | The title of the security incident. | string | +| description | The description of the security incident. | string _(optional)_ | +| externalId | The ID of the security incident in ServiceNow. If present, the security incident is updated. Otherwise, a new security incident is created. | string _(optional)_ | +| priority | The priority in ServiceNow. | string _(optional)_ | +| dest_ip | A list of destination IPs related to the security incident. The IPs will be added as observables to the security incident. | (string \| string[]) _(optional)_ | +| source_ip | A list of source IPs related to the security incident. The IPs will be added as observables to the security incident. | (string \| string[]) _(optional)_ | +| malware_hash | A list of malware hashes related to the security incident. The hashes will be added as observables to the security incident. | (string \| string[]) _(optional)_ | +| malware_url | A list of malware URLs related to the security incident. The URLs will be added as observables to the security incident. | (string \| string[]) _(optional)_ | +| category | The category in ServiceNow. | string _(optional)_ | +| subcategory | The subcategory in ServiceNow. | string _(optional)_ | +| correlation_id | The correlation id of the security incident. | string _(optional)_ | +| correlation_display | The correlation display of the security incident. | string _(optional)_ | + +#### `subActionParams (getFields)` + +No parameters for the `getFields` subaction. Provide an empty object `{}`. + +#### `subActionParams (getIncident)` + +| Property | Description | Type | +| ---------- | ---------------------------------------------- | ------ | +| externalId | The ID of the security incident in ServiceNow. | string | + + +#### `subActionParams (getChoices)` + +| Property | Description | Type | +| -------- | ---------------------------------------------------- | -------- | +| fields | An array of fields. Example: `[priority, category]`. | string[] | + +--- +## ServiceNow ITOM + +The [ServiceNow ITOM user documentation `params`](https://www.elastic.co/guide/en/kibana/master/servicenow-itom-action-type.html) lists configuration properties for the `addEvent` subaction. In addition, several other subaction types are available. +### `params` + +| Property | Description | Type | +| --------------- | ----------------------------------------------------------------- | ------ | +| subAction | The subaction to perform. It can be `addEvent`, and `getChoices`. | string | +| subActionParams | The parameters of the subaction. | object | + +#### `subActionParams (addEvent)` + + +| Property | Description | Type | +| --------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------- | +| source | The name of the event source type. | string _(optional)_ | +| event_class | Specific instance of the source. | string _(optional)_ | +| resource | The name of the resource. | string _(optional)_ | +| node | The Host that the event was triggered for. | string _(optional)_ | +| metric_name | Name of the metric. | string _(optional)_ | +| type | The type of event. | string _(optional)_ | +| severity | The category in ServiceNow. | string _(optional)_ | +| description | The subcategory in ServiceNow. | string _(optional)_ | +| additional_info | Any additional information about the event. | string _(optional)_ | +| message_key | This value is used for de-duplication of events. All actions sharing this key will be associated with the same ServiceNow alert. | string _(optional)_ | +| time_of_event | The time of the event. | string _(optional)_ | + +Refer to [ServiceNow documentation](https://docs.servicenow.com/bundle/rome-it-operations-management/page/product/event-management/task/send-events-via-web-service.html) for more information about the properties. + +#### `subActionParams (getChoices)` + +| Property | Description | Type | +| -------- | ------------------------------------------ | -------- | +| fields | An array of fields. Example: `[severity]`. | string[] | + +--- +## Jira + +The [Jira user documentation `params`](https://www.elastic.co/guide/en/kibana/master/jira-action-type.html) lists configuration properties for the `pushToService` subaction. In addition, several other subaction types are available. +### `params` + +| Property | Description | Type | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------ | +| subAction | The subaction to perform. It can be `pushToService`, `getIncident`, `issueTypes`, `fieldsByIssueType`, `issues`, `issue`, and `getFields`. | string | +| subActionParams | The parameters of the subaction. | object | + +#### `subActionParams (pushToService)` + +| Property | Description | Type | +| -------- | ------------------------------------------------------------------------------------------------------------- | --------------------- | +| incident | The Jira incident. | object | +| comments | The comments of the case. A comment is of the form `{ commentId: string, version: string, comment: string }`. | object[] _(optional)_ | + +The following table describes the properties of the `incident` object. + +| Property | Description | Type | +| ----------- | ------------------------------------------------------------------------------------------------------- | --------------------- | +| summary | The title of the issue. | string | +| description | The description of the issue. | string _(optional)_ | +| externalId | The ID of the issue in Jira. If present, the incident is updated. Otherwise, a new incident is created. | string _(optional)_ | +| issueType | The ID of the issue type in Jira. | string _(optional)_ | +| priority | The name of the priority in Jira. Example: `Medium`. | string _(optional)_ | +| labels | An array of labels. Labels cannot contain spaces. | string[] _(optional)_ | +| parent | The ID or key of the parent issue. Only for `Sub-task` issue types. | string _(optional)_ | + +#### `subActionParams (getIncident)` + +| Property | Description | Type | +| ---------- | ---------------------------- | ------ | +| externalId | The ID of the issue in Jira. | string | + +#### `subActionParams (issueTypes)` + +No parameters for the `issueTypes` subaction. Provide an empty object `{}`. + +#### `subActionParams (fieldsByIssueType)` + +| Property | Description | Type | +| -------- | --------------------------------- | ------ | +| id | The ID of the issue type in Jira. | string | + +#### `subActionParams (issues)` + +| Property | Description | Type | +| -------- | ------------------------ | ------ | +| title | The title to search for. | string | + +#### `subActionParams (issue)` + +| Property | Description | Type | +| -------- | ---------------------------- | ------ | +| id | The ID of the issue in Jira. | string | + +#### `subActionParams (getFields)` + +No parameters for the `getFields` subaction. Provide an empty object `{}`. + +--- + +## IBM Resilient + +The [IBM Resilient user documentation `params`](https://www.elastic.co/guide/en/kibana/master/resilient-action-type.html) lists configuration properties for the `pushToService` subaction. In addition, several other subaction types are available. + +### `params` + +| Property | Description | Type | +| --------------- | ------------------------------------------------------------------------------------------------- | ------ | +| subAction | The subaction to perform. It can be `pushToService`, `getFields`, `incidentTypes`, and `severity. | string | +| subActionParams | The parameters of the subaction. | object | + +#### `subActionParams (pushToService)` + +| Property | Description | Type | +| -------- | ------------------------------------------------------------------------------------------------------------- | --------------------- | +| incident | The IBM Resilient incident. | object | +| comments | The comments of the case. A comment is of the form `{ commentId: string, version: string, comment: string }`. | object[] _(optional)_ | + +The following table describes the properties of the `incident` object. + +| Property | Description | Type | +| ------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------- | +| name | The title of the incident. | string _(optional)_ | +| description | The description of the incident. | string _(optional)_ | +| externalId | The ID of the incident in IBM Resilient. If present, the incident is updated. Otherwise, a new incident is created. | string _(optional)_ | +| incidentTypes | An array with the IDs of IBM Resilient incident types. | number[] _(optional)_ | +| severityCode | IBM Resilient ID of the severity code. | number _(optional)_ | + +#### `subActionParams (getFields)` + +No parameters for the `getFields` subaction. Provide an empty object `{}`. + +#### `subActionParams (incidentTypes)` + +No parameters for the `incidentTypes` subaction. Provide an empty object `{}`. + +#### `subActionParams (severity)` + +No parameters for the `severity` subaction. Provide an empty object `{}`. + +--- +## Swimlane + +Refer to the [Run connector API documentation](https://www.elastic.co/guide/en/kibana/master/execute-connector-api.html#execute-connector-api-request-body) +for the full list of properties. + +### `params` + +| Property | Description | Type | +| --------------- | ---------------------------------------------------- | ------ | +| subAction | The subaction to perform. It can be `pushToService`. | string | +| subActionParams | The parameters of the subaction. | object | + + +`subActionParams (pushToService)` + +| Property | Description | Type | +| -------- | ------------------------------------------------------------------------------------------------------------- | --------------------- | +| incident | The Swimlane incident. | object | +| comments | The comments of the case. A comment is of the form `{ commentId: string, version: string, comment: string }`. | object[] _(optional)_ | + + +The following table describes the properties of the `incident` object. + +| Property | Description | Type | +| ----------- | -------------------------------- | ------------------- | +| alertId | The alert id. | string _(optional)_ | +| caseId | The case id of the incident. | string _(optional)_ | +| caseName | The case name of the incident. | string _(optional)_ | +| description | The description of the incident. | string _(optional)_ | +| ruleName | The rule name. | string _(optional)_ | +| severity | The severity of the incident. | string _(optional)_ | +--- + +# Developing New Connector Types + +When creating a new connector type, your plugin will eventually call `server.plugins.actions.setup.registerType()` to register the type with the `actions` plugin, but there are some additional things to think about about and implement. + +Consider working with the alerting team on early structure /design feedback of new connectors, especially as the APIs and infrastructure are still under development. + +Don't forget to ping @elastic/security-detections-response to see if the new connector should be enabled within their solution. + +## Licensing + +Currently connectors are licensed as "basic" if the connector only interacts with the stack, eg the server log and es index connectors. Other connectors are at least "gold" level. + +## Plugin location + +If the new connector is generic across the stack, it probably belongs in the `stack_connectors` plugin, but if your connector is very specific to a plugin/solution, it might be easiest to implement it in that plugin/solution. + +Connectors that take URLs or hostnames should check that those values are allowed by using the allowed host utilities in the `actions` plugin. + +## Documentation + +You should create asciidoc for the new connector type. Add an entry to the connector type index - [`docs/user/alerting/action-types.asciidoc`](../../../docs/user/alerting/action-types.asciidoc), which points to a new document for the connector type that should be in the directory [`docs/user/alerting/action-types`](../../../docs/user/alerting/action-types). + +We suggest following the template provided in `docs/action-type-template.asciidoc`. The [Email action type](https://www.elastic.co/guide/en/kibana/master/email-action-type.html) is an example of documentation created following the template. + +## Tests + +The connector type should have both unit tests and functional tests. For functional tests, if your connector interacts with a 3rd party service via HTTP, you may be able to create a simulator for your service to test with. See the existing functional test servers in the directory [`x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server`](../../test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server) + +## Connector type config and secrets + +Connector types must define `config` and `secrets` which are used to create connectors. This data should be described with `@kbn/config-schema` object schemas, and you **MUST NOT** use `schema.maybe()` to define properties. + +This is due to the fact that the structures are persisted in saved objects, which performs partial updates on the persisted data. If a property value is already persisted, but an update either doesn't include the property, or sets it to `undefined`, the persisted value will not be changed. Beyond this being a semantic error in general, it also ends up invalidating the encryption used to save secrets, and will render the secrets unable to be unencrypted later. + +Instead of `schema.maybe()`, use `schema.nullable()`, which is the same as `schema.maybe()` except that when passed an `undefined` value, the object returned from the validation will be set to `null`. The resulting type will be `property-type | null`, whereas with `schema.maybe()` it would be `property-type | undefined`. + +## User interface + +To make this connector usable in the Kibana UI, you will need to provide all the UI editing aspects of the connector. The existing connector type user interfaces are defined in [`x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types`](../triggers_actions_ui/public/application/components/builtin_action_types). For more information, see the [UI documentation](../triggers_actions_ui/README.md#create-and-register-new-action-type-ui). \ No newline at end of file diff --git a/x-pack/plugins/stack_connectors/common/index.ts b/x-pack/plugins/stack_connectors/common/index.ts new file mode 100644 index 0000000000000..bdaa075cbfa01 --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/index.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. + */ + +// supported values for `service` in addition to nodemailer's list of well-known services +export enum AdditionalEmailServices { + ELASTIC_CLOUD = 'elastic_cloud', + EXCHANGE = 'exchange_server', + OTHER = 'other', +} + +export const INTERNAL_BASE_STACK_CONNECTORS_API_PATH = '/internal/stack_connectors'; diff --git a/x-pack/plugins/actions/common/servicenow_config.ts b/x-pack/plugins/stack_connectors/common/servicenow_config.ts similarity index 89% rename from x-pack/plugins/actions/common/servicenow_config.ts rename to x-pack/plugins/stack_connectors/common/servicenow_config.ts index 994f6cb33524f..316995ec8bca1 100644 --- a/x-pack/plugins/actions/common/servicenow_config.ts +++ b/x-pack/plugins/stack_connectors/common/servicenow_config.ts @@ -8,10 +8,6 @@ export const serviceNowITSMTable = 'incident'; export const serviceNowSIRTable = 'sn_si_incident'; -export const ServiceNowITSMActionTypeId = '.servicenow'; -export const ServiceNowSIRActionTypeId = '.servicenow-sir'; -export const ServiceNowITOMActionTypeId = '.servicenow-itom'; - const SN_ITSM_APP_ID = '7148dbc91bf1f450ced060a7234bcb88'; const SN_SIR_APP_ID = '2f0746801baeb01019ae54e4604bcb0f'; diff --git a/x-pack/plugins/stack_connectors/jest.config.js b/x-pack/plugins/stack_connectors/jest.config.js new file mode 100644 index 0000000000000..9a343089b9475 --- /dev/null +++ b/x-pack/plugins/stack_connectors/jest.config.js @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/stack_connectors'], + coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/stack_connectors', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/plugins/stack_connectors/{common,server}/**/*.{js,ts,tsx}', + ], +}; diff --git a/x-pack/plugins/stack_connectors/kibana.json b/x-pack/plugins/stack_connectors/kibana.json new file mode 100644 index 0000000000000..a2fc97ad9547e --- /dev/null +++ b/x-pack/plugins/stack_connectors/kibana.json @@ -0,0 +1,13 @@ +{ + "id": "stackConnectors", + "owner": { + "name": "Response Ops", + "githubTeam": "response-ops" + }, + "server": true, + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack", "stack_connectors"], + "requiredPlugins": ["actions"], + "ui": false +} diff --git a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/api.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/api.test.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/cases_webhook/api.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/api.test.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/api.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/api.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/cases_webhook/api.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/api.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/index.ts similarity index 79% rename from x-pack/plugins/actions/server/builtin_action_types/cases_webhook/index.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/index.ts index 821ab792308c1..3b32e79ac725c 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/index.ts @@ -7,7 +7,12 @@ import { curry } from 'lodash'; import { Logger } from '@kbn/core/server'; -import { CasesConnectorFeatureId } from '../../../common'; +import type { + ActionType as ConnectorType, + ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, + ActionTypeExecutorResult as ConnectorTypeExecutorResult, +} from '@kbn/actions-plugin/server/types'; +import { CasesConnectorFeatureId } from '@kbn/actions-plugin/common/connector_feature_config'; import { CasesWebhookActionParamsType, CasesWebhookExecutorResultData, @@ -16,8 +21,6 @@ import { ExecutorParams, ExecutorSubActionPushParams, } from './types'; -import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../../types'; -import { ActionsConfigurationUtilities } from '../../actions_config'; import { createExternalService } from './service'; import { ExecutorParamsSchema, @@ -30,22 +33,20 @@ import * as i18n from './translations'; const supportedSubActions: string[] = ['pushToService']; export type ActionParamsType = CasesWebhookActionParamsType; -export const ActionTypeId = '.cases-webhook'; -// action type definition -export function getActionType({ +export const ConnectorTypeId = '.cases-webhook'; +// connector type definition +export function getConnectorType({ logger, - configurationUtilities, }: { logger: Logger; - configurationUtilities: ActionsConfigurationUtilities; -}): ActionType< +}): ConnectorType< CasesWebhookPublicConfigurationType, CasesWebhookSecretConfigurationType, ExecutorParams, CasesWebhookExecutorResultData > { return { - id: ActionTypeId, + id: ConnectorTypeId, minimumLicenseRequired: 'gold', name: i18n.NAME, validate: { @@ -62,24 +63,22 @@ export function getActionType({ }, connector: validate.connector, }, - executor: curry(executor)({ logger, configurationUtilities }), + executor: curry(executor)({ logger }), supportedFeatureIds: [CasesConnectorFeatureId], }; } // action executor export async function executor( - { - logger, - configurationUtilities, - }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, - execOptions: ActionTypeExecutorOptions< + { logger }: { logger: Logger }, + execOptions: ConnectorTypeExecutorOptions< CasesWebhookPublicConfigurationType, CasesWebhookSecretConfigurationType, CasesWebhookActionParamsType > -): Promise> { +): Promise> { const actionId = execOptions.actionId; + const configurationUtilities = execOptions.configurationUtilities; const { subAction, subActionParams } = execOptions.params; let data: CasesWebhookExecutorResultData | undefined; diff --git a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/mock.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/mock.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/cases_webhook/mock.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/mock.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/schema.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/schema.ts similarity index 98% rename from x-pack/plugins/actions/server/builtin_action_types/cases_webhook/schema.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/schema.ts index 1fb3edca6e4b7..fb8d7f8a05d28 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/schema.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/schema.ts @@ -7,7 +7,7 @@ import { schema } from '@kbn/config-schema'; import { CasesWebhookMethods } from './types'; -import { nullableType } from '../lib/nullable'; +import { nullableType } from '../../lib/nullable'; const HeadersSchema = schema.recordOf(schema.string(), schema.string()); diff --git a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/service.test.ts similarity index 98% rename from x-pack/plugins/actions/server/builtin_action_types/cases_webhook/service.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/service.test.ts index 2d79819d3cc4a..8e5c34e1e9ca5 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/service.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/service.test.ts @@ -8,15 +8,15 @@ import axios, { AxiosError, AxiosResponse } from 'axios'; import { createExternalService } from './service'; -import { request, createAxiosResponse } from '../../lib/axios_utils'; +import { request, createAxiosResponse } from '@kbn/actions-plugin/server/lib/axios_utils'; import { CasesWebhookMethods, CasesWebhookPublicConfigurationType, ExternalService } from './types'; import { Logger } from '@kbn/core/server'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { actionsConfigMock } from '../../actions_config.mock'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; const logger = loggingSystemMock.create().get() as jest.Mocked; -jest.mock('../../lib/axios_utils', () => { - const originalUtils = jest.requireActual('../../lib/axios_utils'); +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(), diff --git a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/service.ts similarity index 97% rename from x-pack/plugins/actions/server/builtin_action_types/cases_webhook/service.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/service.ts index 6e657d8b21d9e..518debe43a002 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/service.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/service.ts @@ -9,8 +9,10 @@ import axios, { AxiosResponse } from 'axios'; import { Logger } from '@kbn/core/server'; import { isString } from 'lodash'; +import { renderMustacheStringNoEscape } from '@kbn/actions-plugin/server/lib/mustache_renderer'; +import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; import { validateAndNormalizeUrl, validateJson } from './validators'; -import { renderMustacheStringNoEscape } from '../../lib/mustache_renderer'; import { createServiceError, getObjectValueByKeyAsString, @@ -31,8 +33,6 @@ import { } from './types'; import * as i18n from './translations'; -import { request } from '../../lib/axios_utils'; -import { ActionsConfigurationUtilities } from '../../actions_config'; export const createExternalService = ( actionId: string, diff --git a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/translations.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/translations.ts similarity index 71% rename from x-pack/plugins/actions/server/builtin_action_types/cases_webhook/translations.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/translations.ts index 06fd81491c004..207230016ea30 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/translations.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/translations.ts @@ -7,12 +7,12 @@ import { i18n } from '@kbn/i18n'; -export const NAME = i18n.translate('xpack.actions.builtin.cases.casesWebhookTitle', { +export const NAME = i18n.translate('xpack.stackConnectors.casesWebhook.title', { defaultMessage: 'Webhook - Case Management', }); export const INVALID_URL = (err: string, url: string) => - i18n.translate('xpack.actions.builtin.casesWebhook.casesWebhookConfigurationErrorNoHostname', { + i18n.translate('xpack.stackConnectors.casesWebhook.configurationErrorNoHostname', { defaultMessage: 'error configuring cases webhook action: unable to parse {url}: {err}', values: { err, @@ -21,7 +21,7 @@ export const INVALID_URL = (err: string, url: string) => }); export const CONFIG_ERR = (err: string) => - i18n.translate('xpack.actions.builtin.casesWebhook.casesWebhookConfigurationError', { + i18n.translate('xpack.stackConnectors.casesWebhook.configurationError', { defaultMessage: 'error configuring cases webhook action: {err}', values: { err, @@ -29,14 +29,14 @@ export const CONFIG_ERR = (err: string) => }); export const INVALID_USER_PW = i18n.translate( - 'xpack.actions.builtin.casesWebhook.invalidUsernamePassword', + 'xpack.stackConnectors.casesWebhook.invalidUsernamePassword', { defaultMessage: 'both user and password must be specified', } ); export const ALLOWED_HOSTS_ERROR = (message: string) => - i18n.translate('xpack.actions.builtin.casesWebhook.configuration.apiAllowedHostsError', { + i18n.translate('xpack.stackConnectors.casesWebhook.configuration.apiAllowedHostsError', { defaultMessage: 'error configuring connector action: {message}', values: { message, diff --git a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/types.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/types.ts similarity index 98% rename from x-pack/plugins/actions/server/builtin_action_types/cases_webhook/types.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/types.ts index aae733d4ed94b..f9fde29f252e0 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/types.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/types.ts @@ -7,7 +7,7 @@ import { TypeOf } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; -import { ValidatorServices } from '../../types'; +import { ValidatorServices } from '@kbn/actions-plugin/server/types'; import { ExecutorParamsSchema, ExecutorSubActionPushParamsSchema, diff --git a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/utils.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/utils.test.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/cases_webhook/utils.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/utils.test.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/utils.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/utils.ts similarity index 97% rename from x-pack/plugins/actions/server/builtin_action_types/cases_webhook/utils.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/utils.ts index 3466e73bdbe5d..108674d5a5a11 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/utils.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/utils.ts @@ -7,7 +7,7 @@ import { AxiosResponse, AxiosError } from 'axios'; import { isEmpty, isObjectLike, get } from 'lodash'; -import { getErrorMessage } from '../../lib/axios_utils'; +import { getErrorMessage } from '@kbn/actions-plugin/server/lib/axios_utils'; import * as i18n from './translations'; export const createServiceError = (error: AxiosError, message: string) => { diff --git a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/validators.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/validators.ts similarity index 95% rename from x-pack/plugins/actions/server/builtin_action_types/cases_webhook/validators.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/validators.ts index 22113f0f7626e..95265cfcaf6ef 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/cases_webhook/validators.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/validators.ts @@ -5,14 +5,14 @@ * 2.0. */ +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { ValidatorServices } from '@kbn/actions-plugin/server/types'; import * as i18n from './translations'; -import { ActionsConfigurationUtilities } from '../../actions_config'; import { CasesWebhookPublicConfigurationType, CasesWebhookSecretConfigurationType, ExternalServiceValidation, } from './types'; -import { ValidatorServices } from '../../types'; const validateConfig = ( configObject: CasesWebhookPublicConfigurationType, diff --git a/x-pack/plugins/stack_connectors/server/connector_types/cases/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/index.ts new file mode 100644 index 0000000000000..abc6c0557a780 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/index.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. + */ +export { + getConnectorType as getCasesWebhookConnectorType, + ConnectorTypeId as CasesWebhookConnectorTypeId, +} from './cases_webhook'; +export type { ActionParamsType as CasesWebhookActionParams } from './cases_webhook'; + +export { + getConnectorType as getJiraConnectorType, + ConnectorTypeId as JiraConnectorTypeId, +} from './jira'; +export type { ActionParamsType as JiraActionParams } from './jira'; + +export { + getConnectorType as getResilientConnectorType, + ConnectorTypeId as ResilientConnectorTypeId, +} from './resilient'; +export type { ActionParamsType as ResilientActionParams } from './resilient'; + +export { + getServiceNowITSMConnectorType, + getServiceNowSIRConnectorType, + getServiceNowITOMConnectorType, + ServiceNowITSMConnectorTypeId, + ServiceNowSIRConnectorTypeId, + ServiceNowITOMConnectorTypeId, +} from './servicenow'; +export type { ActionParamsType as ServiceNowActionParams } from './servicenow'; + +export { getConnectorType as getSwimlaneConnectorType } from './swimlane'; + +export { + getConnectorType as getXmattersConnectorType, + ConnectorTypeId as XmattersConnectorTypeId, +} from './xmatters'; +export type { ActionParamsType as XmattersActionParams } from './xmatters'; diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/api.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/api.test.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/jira/api.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/jira/api.test.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/api.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/api.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/jira/api.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/jira/api.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/index.ts similarity index 84% rename from x-pack/plugins/actions/server/builtin_action_types/jira/index.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/jira/index.ts index 86ed10f57d20b..d2130c085cda1 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/index.ts @@ -9,14 +9,23 @@ import { curry } from 'lodash'; import { TypeOf } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; +import type { + ActionType as ConnectorType, + ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, + ActionTypeExecutorResult as ConnectorTypeExecutorResult, +} from '@kbn/actions-plugin/server/types'; +import { + AlertingConnectorFeatureId, + CasesConnectorFeatureId, + UptimeConnectorFeatureId, + SecurityConnectorFeatureId, +} from '@kbn/actions-plugin/common/types'; import { validate } from './validators'; import { ExternalIncidentServiceConfigurationSchema, ExternalIncidentServiceSecretConfigurationSchema, ExecutorParamsSchema, } from './schema'; -import { ActionsConfigurationUtilities } from '../../actions_config'; -import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../../types'; import { createExternalService } from './service'; import { api } from './api'; import { @@ -32,17 +41,10 @@ import { ExecutorSubActionGetIncidentParams, } from './types'; import * as i18n from './translations'; -import { - AlertingConnectorFeatureId, - CasesConnectorFeatureId, - UptimeConnectorFeatureId, - SecurityConnectorFeatureId, -} from '../../../common'; export type ActionParamsType = TypeOf; -interface GetActionTypeParams { +interface GetConnectorTypeParams { logger: Logger; - configurationUtilities: ActionsConfigurationUtilities; } const supportedSubActions: string[] = [ @@ -55,19 +57,19 @@ const supportedSubActions: string[] = [ 'issue', ]; -export const ActionTypeId = '.jira'; -// action type definition -export function getActionType( - params: GetActionTypeParams -): ActionType< +export const ConnectorTypeId = '.jira'; +// connector type definition +export function getConnectorType( + params: GetConnectorTypeParams +): ConnectorType< JiraPublicConfigurationType, JiraSecretConfigurationType, ExecutorParams, JiraExecutorResultData | {} > { - const { logger, configurationUtilities } = params; + const { logger } = params; return { - id: ActionTypeId, + id: ConnectorTypeId, minimumLicenseRequired: 'gold', name: i18n.NAME, supportedFeatureIds: [ @@ -89,23 +91,20 @@ export function getActionType( schema: ExecutorParamsSchema, }, }, - executor: curry(executor)({ logger, configurationUtilities }), + executor: curry(executor)({ logger }), }; } // action executor async function executor( - { - logger, - configurationUtilities, - }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, - execOptions: ActionTypeExecutorOptions< + { logger }: { logger: Logger }, + execOptions: ConnectorTypeExecutorOptions< JiraPublicConfigurationType, JiraSecretConfigurationType, ExecutorParams > -): Promise> { - const { actionId, config, params, secrets } = execOptions; +): Promise> { + const { actionId, config, params, secrets, configurationUtilities } = execOptions; const { subAction, subActionParams } = params as ExecutorParams; let data: JiraExecutorResultData | null = null; diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/mocks.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/mocks.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/jira/mocks.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/jira/mocks.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/schema.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/schema.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/jira/schema.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/jira/schema.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/service.test.ts similarity index 99% rename from x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/jira/service.test.ts index 59f00826cae43..9dcd4f2ce2af2 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/service.test.ts @@ -8,11 +8,11 @@ import axios from 'axios'; import { createExternalService } from './service'; -import { request, createAxiosResponse } from '../../lib/axios_utils'; +import { request, createAxiosResponse } from '@kbn/actions-plugin/server/lib/axios_utils'; import { ExternalService } from './types'; import { Logger } from '@kbn/core/server'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { actionsConfigMock } from '../../actions_config.mock'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; const logger = loggingSystemMock.create().get() as jest.Mocked; interface ResponseError extends Error { @@ -20,8 +20,8 @@ interface ResponseError extends Error { } jest.mock('axios'); -jest.mock('../../lib/axios_utils', () => { - const originalUtils = jest.requireActual('../../lib/axios_utils'); +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(), diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/service.ts similarity index 98% rename from x-pack/plugins/actions/server/builtin_action_types/jira/service.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/jira/service.ts index 948fab0586a28..b0d7571b302cd 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/service.ts @@ -9,6 +9,12 @@ import axios from 'axios'; import { isEmpty } from 'lodash'; import { Logger } from '@kbn/core/server'; +import { + request, + getErrorMessage, + throwIfResponseIsNotValid, +} from '@kbn/actions-plugin/server/lib/axios_utils'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; import { CreateCommentParams, CreateIncidentParams, @@ -27,8 +33,6 @@ import { } from './types'; import * as i18n from './translations'; -import { request, getErrorMessage, throwIfResponseIsNotValid } from '../../lib/axios_utils'; -import { ActionsConfigurationUtilities } from '../../actions_config'; const VERSION = '2'; const BASE_URL = `rest/api/${VERSION}`; diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/translations.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/translations.ts similarity index 78% rename from x-pack/plugins/actions/server/builtin_action_types/jira/translations.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/jira/translations.ts index f3baefc9e2900..1ca5ec985d1f0 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/translations.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/translations.ts @@ -7,12 +7,12 @@ import { i18n } from '@kbn/i18n'; -export const NAME = i18n.translate('xpack.actions.builtin.cases.jiraTitle', { +export const NAME = i18n.translate('xpack.stackConnectors.jira.title', { defaultMessage: 'Jira', }); export const ALLOWED_HOSTS_ERROR = (message: string) => - i18n.translate('xpack.actions.builtin.jira.configuration.apiAllowedHostsError', { + i18n.translate('xpack.stackConnectors.jira.configuration.apiAllowedHostsError', { defaultMessage: 'error configuring connector action: {message}', values: { message, diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/types.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/types.ts similarity index 99% rename from x-pack/plugins/actions/server/builtin_action_types/jira/types.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/jira/types.ts index 75b5f5a2ef30a..c975e23b1b783 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/types.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/types.ts @@ -9,6 +9,7 @@ import { TypeOf } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; +import { ValidatorServices } from '@kbn/actions-plugin/server/types'; import { ExternalIncidentServiceConfigurationSchema, ExternalIncidentServiceSecretConfigurationSchema, @@ -22,7 +23,6 @@ import { ExecutorSubActionGetIssueParamsSchema, ExecutorSubActionCommonFieldsParamsSchema, } from './schema'; -import { ValidatorServices } from '../../types'; export type JiraPublicConfigurationType = TypeOf; export type JiraSecretConfigurationType = TypeOf< diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/validators.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/validators.ts similarity index 93% rename from x-pack/plugins/actions/server/builtin_action_types/jira/validators.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/jira/validators.ts index 4195920703248..894f457947b71 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/validators.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/jira/validators.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ValidatorServices } from '@kbn/actions-plugin/server/types'; import { JiraPublicConfigurationType, JiraSecretConfigurationType, @@ -12,7 +13,6 @@ import { } from './types'; import * as i18n from './translations'; -import { ValidatorServices } from '../../types'; export const validateCommonConfig = ( configObject: JiraPublicConfigurationType, diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/api.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/api.test.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/resilient/api.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/api.test.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/api.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/resilient/api.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/api.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/index.ts similarity index 81% rename from x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/index.ts index b0553a63239c2..081fbe92502d9 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/index.ts @@ -9,14 +9,22 @@ import { curry } from 'lodash'; import { TypeOf } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; +import type { + ActionType as ConnectorType, + ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, + ActionTypeExecutorResult as ConnectorTypeExecutorResult, +} from '@kbn/actions-plugin/server/types'; +import { + AlertingConnectorFeatureId, + CasesConnectorFeatureId, + SecurityConnectorFeatureId, +} from '@kbn/actions-plugin/common/types'; import { validate } from './validators'; import { ExternalIncidentServiceConfigurationSchema, ExternalIncidentServiceSecretConfigurationSchema, ExecutorParamsSchema, } from './schema'; -import { ActionsConfigurationUtilities } from '../../actions_config'; -import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../../types'; import { createExternalService } from './service'; import { api } from './api'; import { @@ -30,34 +38,28 @@ import { ExecutorSubActionCommonFieldsParams, } from './types'; import * as i18n from './translations'; -import { - AlertingConnectorFeatureId, - CasesConnectorFeatureId, - SecurityConnectorFeatureId, -} from '../../../common'; export type ActionParamsType = TypeOf; -interface GetActionTypeParams { +interface GetConnectorTypeParams { logger: Logger; - configurationUtilities: ActionsConfigurationUtilities; } const supportedSubActions: string[] = ['getFields', 'pushToService', 'incidentTypes', 'severity']; -export const ActionTypeId = '.resilient'; -// action type definition -export function getActionType( - params: GetActionTypeParams -): ActionType< +export const ConnectorTypeId = '.resilient'; +// connector type definition +export function getConnectorType( + params: GetConnectorTypeParams +): ConnectorType< ResilientPublicConfigurationType, ResilientSecretConfigurationType, ExecutorParams, ResilientExecutorResultData | {} > { - const { logger, configurationUtilities } = params; + const { logger } = params; return { - id: ActionTypeId, + id: ConnectorTypeId, minimumLicenseRequired: 'platinum', name: i18n.NAME, supportedFeatureIds: [ @@ -78,23 +80,20 @@ export function getActionType( schema: ExecutorParamsSchema, }, }, - executor: curry(executor)({ logger, configurationUtilities }), + executor: curry(executor)({ logger }), }; } // action executor async function executor( - { - logger, - configurationUtilities, - }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, - execOptions: ActionTypeExecutorOptions< + { logger }: { logger: Logger }, + execOptions: ConnectorTypeExecutorOptions< ResilientPublicConfigurationType, ResilientSecretConfigurationType, ExecutorParams > -): Promise> { - const { actionId, config, params, secrets } = execOptions; +): Promise> { + const { actionId, config, params, secrets, configurationUtilities } = execOptions; const { subAction, subActionParams } = params as ExecutorParams; let data: ResilientExecutorResultData | null = null; diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/mocks.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/resilient/mocks.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/mocks.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/schema.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/resilient/schema.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/schema.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/service.test.ts similarity index 98% rename from x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/service.test.ts index 2d577771c57f3..b45cf9b1e34fe 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/service.test.ts @@ -8,18 +8,18 @@ import axios from 'axios'; import { createExternalService, getValueTextContent, formatUpdateRequest } from './service'; -import { request, createAxiosResponse } from '../../lib/axios_utils'; +import { request, createAxiosResponse } from '@kbn/actions-plugin/server/lib/axios_utils'; import { ExternalService } from './types'; import { Logger } from '@kbn/core/server'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { incidentTypes, resilientFields, severity } from './mocks'; -import { actionsConfigMock } from '../../actions_config.mock'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; const logger = loggingSystemMock.create().get() as jest.Mocked; jest.mock('axios'); -jest.mock('../../lib/axios_utils', () => { - const originalUtils = jest.requireActual('../../lib/axios_utils'); +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(), diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/service.ts similarity index 97% rename from x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/service.ts index f99ce7f1a0ef1..1e668348e9975 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/service.ts @@ -9,6 +9,12 @@ import axios from 'axios'; import { omitBy, isNil } from 'lodash/fp'; import { Logger } from '@kbn/core/server'; +import { + getErrorMessage, + request, + throwIfResponseIsNotValid, +} from '@kbn/actions-plugin/server/lib/axios_utils'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; import { ExternalServiceCredentials, ExternalService, @@ -24,8 +30,6 @@ import { } from './types'; import * as i18n from './translations'; -import { getErrorMessage, request, throwIfResponseIsNotValid } from '../../lib/axios_utils'; -import { ActionsConfigurationUtilities } from '../../actions_config'; const VIEW_INCIDENT_URL = `#incidents`; diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/translations.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/translations.ts similarity index 74% rename from x-pack/plugins/actions/server/builtin_action_types/resilient/translations.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/translations.ts index fcb800c1499db..e6f75e5b9d05f 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/translations.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/translations.ts @@ -7,12 +7,12 @@ import { i18n } from '@kbn/i18n'; -export const NAME = i18n.translate('xpack.actions.builtin.cases.resilientTitle', { +export const NAME = i18n.translate('xpack.stackConnectors.resilient.title', { defaultMessage: 'IBM Resilient', }); export const ALLOWED_HOSTS_ERROR = (message: string) => - i18n.translate('xpack.actions.builtin.configuration.apiAllowedHostsError', { + i18n.translate('xpack.stackConnectors.resilient.configuration.apiAllowedHostsError', { defaultMessage: 'error configuring connector action: {message}', values: { message, diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/types.ts similarity index 98% rename from x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/types.ts index 2528e1742e1b9..336e899380c56 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/types.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/types.ts @@ -9,6 +9,7 @@ import { TypeOf } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; +import { ValidatorServices } from '@kbn/actions-plugin/server/types'; import { ExecutorParamsSchema, ExecutorSubActionCommonFieldsParamsSchema, @@ -21,8 +22,6 @@ import { ExternalIncidentServiceSecretConfigurationSchema, } from './schema'; -import { ValidatorServices } from '../../types'; - export type ResilientPublicConfigurationType = TypeOf< typeof ExternalIncidentServiceConfigurationSchema >; diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/validators.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/validators.ts similarity index 93% rename from x-pack/plugins/actions/server/builtin_action_types/resilient/validators.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/validators.ts index 5d70aea1660a1..335b8f6b405ad 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/validators.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/resilient/validators.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ValidatorServices } from '@kbn/actions-plugin/server/types'; import { ResilientPublicConfigurationType, ResilientSecretConfigurationType, @@ -12,7 +13,6 @@ import { } from './types'; import * as i18n from './translations'; -import { ValidatorServices } from '../../types'; export const validateCommonConfig = ( configObject: ResilientPublicConfigurationType, diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/api.test.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/api.test.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/api.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/api.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api_itom.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/api_itom.test.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/api_itom.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/api_itom.test.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api_itom.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/api_itom.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/api_itom.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/api_itom.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api_sir.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/api_sir.test.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/api_sir.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/api_sir.test.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api_sir.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/api_sir.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/api_sir.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/api_sir.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/config.test.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/config.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/config.test.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/config.ts similarity index 87% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/config.ts index 11f18f0407fdf..4fae325fec2f9 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/config.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/config.ts @@ -10,9 +10,9 @@ import { SNProductsConfig } from './types'; export const serviceNowITSMTable = 'incident'; export const serviceNowSIRTable = 'sn_si_incident'; -export const ServiceNowITSMActionTypeId = '.servicenow'; -export const ServiceNowSIRActionTypeId = '.servicenow-sir'; -export const ServiceNowITOMActionTypeId = '.servicenow-itom'; +export const ServiceNowITSMConnectorTypeId = '.servicenow'; +export const ServiceNowSIRConnectorTypeId = '.servicenow-sir'; +export const ServiceNowITOMConnectorTypeId = '.servicenow-itom'; const SN_ITSM_APP_ID = '7148dbc91bf1f450ced060a7234bcb88'; const SN_SIR_APP_ID = '2f0746801baeb01019ae54e4604bcb0f'; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/create_service_wrapper.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/create_service_wrapper.test.ts similarity index 92% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/create_service_wrapper.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/create_service_wrapper.test.ts index 37ada260c75a9..311de65eb4abe 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/create_service_wrapper.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/create_service_wrapper.test.ts @@ -9,8 +9,8 @@ import axios from 'axios'; import { createServiceWrapper } from './create_service_wrapper'; import { Logger } from '@kbn/core/server'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { actionsConfigMock } from '../../actions_config.mock'; -import { connectorTokenClientMock } from '../lib/connector_token_client.mock'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { connectorTokenClientMock } from '@kbn/actions-plugin/server/lib/connector_token_client.mock'; import { snExternalServiceConfig } from './config'; const logger = loggingSystemMock.create().get() as jest.Mocked; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/create_service_wrapper.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/create_service_wrapper.ts similarity index 89% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/create_service_wrapper.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/create_service_wrapper.ts index cd431027a720f..f2de6e3787f70 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/create_service_wrapper.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/create_service_wrapper.ts @@ -6,12 +6,12 @@ */ import { Logger } from '@kbn/core/server'; +import type { ConnectorTokenClientContract } from '@kbn/actions-plugin/server/types'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; import { ExternalService, ExternalServiceCredentials, SNProductsConfigValue } from './types'; import { ServiceNowPublicConfigurationType, ServiceFactory } from './types'; -import { ActionsConfigurationUtilities } from '../../actions_config'; import { getAxiosInstance } from './utils'; -import { ConnectorTokenClientContract } from '../../types'; interface CreateServiceWrapperOpts { connectorId: string; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/index.test.ts similarity index 62% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/index.test.ts index 6ba8b80dfc09c..02cc32111bff4 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/index.test.ts @@ -5,19 +5,15 @@ * 2.0. */ -import { actionsMock } from '../../mocks'; -import { createActionTypeRegistry } from '../index.test'; +import { Logger } from '@kbn/core/server'; +import { loggerMock } from '@kbn/logging-mocks'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; +import { ExecutorParams, ServiceNowPublicConfigurationType } from './types'; import { - ServiceNowPublicConfigurationBaseType, - ServiceNowSecretConfigurationType, - ExecutorParams, - PushToServiceResponse, -} from './types'; -import { - ServiceNowActionType, - ServiceNowITSMActionTypeId, - ServiceNowSIRActionTypeId, - ServiceNowActionTypeExecutorOptions, + ServiceNowConnectorType, + ServiceNowConnectorTypeExecutorOptions, + getServiceNowITSMConnectorType, + getServiceNowSIRConnectorType, } from '.'; import { api } from './api'; @@ -32,6 +28,7 @@ jest.mock('./api', () => ({ })); const services = actionsMock.createServices(); +const mockedLogger: jest.Mocked = loggerMock.create(); describe('ServiceNow', () => { const config = { apiUrl: 'https://instance.com' }; @@ -51,16 +48,11 @@ describe('ServiceNow', () => { }); describe('ServiceNow ITSM', () => { - let actionType: ServiceNowActionType; - + let connectorType: ServiceNowConnectorType; beforeAll(() => { - const { actionTypeRegistry } = createActionTypeRegistry(); - actionType = actionTypeRegistry.get< - ServiceNowPublicConfigurationBaseType, - ServiceNowSecretConfigurationType, - ExecutorParams, - PushToServiceResponse | {} - >(ServiceNowITSMActionTypeId); + connectorType = getServiceNowITSMConnectorType({ + logger: mockedLogger, + }); }); describe('execute()', () => { @@ -76,8 +68,11 @@ describe('ServiceNow', () => { secrets, params, services, - } as unknown as ServiceNowActionTypeExecutorOptions; - await actionType.executor(executorOptions); + } as unknown as ServiceNowConnectorTypeExecutorOptions< + ServiceNowPublicConfigurationType, + ExecutorParams + >; + await connectorType.executor(executorOptions); expect((api.pushToService as jest.Mock).mock.calls[0][0].commentFieldKey).toBe( 'work_notes' ); @@ -86,16 +81,12 @@ describe('ServiceNow', () => { }); describe('ServiceNow SIR', () => { - let actionType: ServiceNowActionType; + let connectorType: ServiceNowConnectorType; beforeAll(() => { - const { actionTypeRegistry } = createActionTypeRegistry(); - actionType = actionTypeRegistry.get< - ServiceNowPublicConfigurationBaseType, - ServiceNowSecretConfigurationType, - ExecutorParams, - PushToServiceResponse | {} - >(ServiceNowSIRActionTypeId); + connectorType = getServiceNowSIRConnectorType({ + logger: mockedLogger, + }); }); describe('execute()', () => { @@ -111,8 +102,11 @@ describe('ServiceNow', () => { secrets, params, services, - } as unknown as ServiceNowActionTypeExecutorOptions; - await actionType.executor(executorOptions); + } as unknown as ServiceNowConnectorTypeExecutorOptions< + ServiceNowPublicConfigurationType, + ExecutorParams + >; + await connectorType.executor(executorOptions); expect((api.pushToService as jest.Mock).mock.calls[0][0].commentFieldKey).toBe( 'work_notes' ); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/index.ts similarity index 78% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/index.ts index cb008de5587d6..493c7024bb15f 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/index.ts @@ -9,6 +9,17 @@ import { curry } from 'lodash'; import { TypeOf } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; +import type { + ActionType as ConnectorType, + ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, + ActionTypeExecutorResult as ConnectorTypeExecutorResult, +} from '@kbn/actions-plugin/server/types'; +import { + AlertingConnectorFeatureId, + CasesConnectorFeatureId, + UptimeConnectorFeatureId, + SecurityConnectorFeatureId, +} from '@kbn/actions-plugin/common/types'; import { validate } from './validators'; import { ExternalIncidentServiceConfigurationSchema, @@ -18,8 +29,6 @@ import { ExecutorParamsSchemaSIR, ExecutorParamsSchemaITOM, } from './schema'; -import { ActionsConfigurationUtilities } from '../../actions_config'; -import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../../types'; import { createExternalService } from './service'; import { api as commonAPI } from './api'; import * as i18n from './translations'; @@ -42,10 +51,10 @@ import { ExternalService, } from './types'; import { - ServiceNowITOMActionTypeId, - ServiceNowITSMActionTypeId, + ServiceNowITOMConnectorTypeId, + ServiceNowITSMConnectorTypeId, serviceNowITSMTable, - ServiceNowSIRActionTypeId, + ServiceNowSIRConnectorTypeId, serviceNowSIRTable, snExternalServiceConfig, } from './config'; @@ -55,47 +64,40 @@ import { throwIfSubActionIsNotSupported } from './utils'; import { createExternalServiceITOM } from './service_itom'; import { apiITOM } from './api_itom'; import { createServiceWrapper } from './create_service_wrapper'; -import { - AlertingConnectorFeatureId, - CasesConnectorFeatureId, - UptimeConnectorFeatureId, - SecurityConnectorFeatureId, -} from '../../../common'; export { - ServiceNowITSMActionTypeId, + ServiceNowITSMConnectorTypeId, serviceNowITSMTable, - ServiceNowSIRActionTypeId, + ServiceNowSIRConnectorTypeId, serviceNowSIRTable, - ServiceNowITOMActionTypeId, + ServiceNowITOMConnectorTypeId, }; export type ActionParamsType = | TypeOf | TypeOf; -interface GetActionTypeParams { +interface GetConnectorTypeParams { logger: Logger; - configurationUtilities: ActionsConfigurationUtilities; } -export type ServiceNowActionType< +export type ServiceNowConnectorType< C extends Record = ServiceNowPublicConfigurationBaseType, T extends Record = ExecutorParams -> = ActionType; +> = ConnectorType; -export type ServiceNowActionTypeExecutorOptions< +export type ServiceNowConnectorTypeExecutorOptions< C extends Record = ServiceNowPublicConfigurationBaseType, T extends Record = ExecutorParams -> = ActionTypeExecutorOptions; +> = ConnectorTypeExecutorOptions; -// action type definition -export function getServiceNowITSMActionType( - params: GetActionTypeParams -): ServiceNowActionType { - const { logger, configurationUtilities } = params; +// connector type definition +export function getServiceNowITSMConnectorType( + params: GetConnectorTypeParams +): ServiceNowConnectorType { + const { logger } = params; return { - id: ServiceNowITSMActionTypeId, + id: ServiceNowITSMConnectorTypeId, minimumLicenseRequired: 'platinum', name: i18n.SERVICENOW_ITSM, supportedFeatureIds: [ @@ -120,20 +122,19 @@ export function getServiceNowITSMActionType( }, executor: curry(executor)({ logger, - configurationUtilities, - actionTypeId: ServiceNowITSMActionTypeId, + actionTypeId: ServiceNowITSMConnectorTypeId, createService: createExternalService, api: commonAPI, }), }; } -export function getServiceNowSIRActionType( - params: GetActionTypeParams -): ServiceNowActionType { - const { logger, configurationUtilities } = params; +export function getServiceNowSIRConnectorType( + params: GetConnectorTypeParams +): ServiceNowConnectorType { + const { logger } = params; return { - id: ServiceNowSIRActionTypeId, + id: ServiceNowSIRConnectorTypeId, minimumLicenseRequired: 'platinum', name: i18n.SERVICENOW_SIR, supportedFeatureIds: [ @@ -157,20 +158,19 @@ export function getServiceNowSIRActionType( }, executor: curry(executor)({ logger, - configurationUtilities, - actionTypeId: ServiceNowSIRActionTypeId, + actionTypeId: ServiceNowSIRConnectorTypeId, createService: createExternalServiceSIR, api: apiSIR, }), }; } -export function getServiceNowITOMActionType( - params: GetActionTypeParams -): ServiceNowActionType { - const { logger, configurationUtilities } = params; +export function getServiceNowITOMConnectorType( + params: GetConnectorTypeParams +): ServiceNowConnectorType { + const { logger } = params; return { - id: ServiceNowITOMActionTypeId, + id: ServiceNowITOMConnectorTypeId, minimumLicenseRequired: 'platinum', name: i18n.SERVICENOW_ITOM, supportedFeatureIds: [AlertingConnectorFeatureId, SecurityConnectorFeatureId], @@ -190,8 +190,7 @@ export function getServiceNowITOMActionType( }, executor: curry(executorITOM)({ logger, - configurationUtilities, - actionTypeId: ServiceNowITOMActionTypeId, + actionTypeId: ServiceNowITOMConnectorTypeId, createService: createExternalServiceITOM, api: apiITOM, }), @@ -203,23 +202,21 @@ const supportedSubActions: string[] = ['getFields', 'pushToService', 'getChoices async function executor( { logger, - configurationUtilities, actionTypeId, createService, api, }: { logger: Logger; - configurationUtilities: ActionsConfigurationUtilities; actionTypeId: string; createService: ServiceFactory; api: ExternalServiceAPI; }, - execOptions: ServiceNowActionTypeExecutorOptions< + execOptions: ServiceNowConnectorTypeExecutorOptions< ServiceNowPublicConfigurationType, ExecutorParams > -): Promise> { - const { actionId, config, params, secrets, services } = execOptions; +): Promise> { + const { actionId, config, params, secrets, services, configurationUtilities } = execOptions; const { subAction, subActionParams } = params; const connectorTokenClient = services.connectorTokenClient; const externalServiceConfig = snExternalServiceConfig[actionTypeId]; @@ -281,23 +278,21 @@ const supportedSubActionsITOM = ['addEvent', 'getChoices']; async function executorITOM( { logger, - configurationUtilities, actionTypeId, createService, api, }: { logger: Logger; - configurationUtilities: ActionsConfigurationUtilities; actionTypeId: string; createService: ServiceFactory; api: ExternalServiceApiITOM; }, - execOptions: ServiceNowActionTypeExecutorOptions< + execOptions: ServiceNowConnectorTypeExecutorOptions< ServiceNowPublicConfigurationBaseType, ExecutorParamsITOM > -): Promise> { - const { actionId, config, params, secrets } = execOptions; +): Promise> { + const { actionId, config, params, secrets, configurationUtilities } = execOptions; const { subAction, subActionParams } = params; const connectorTokenClient = execOptions.services.connectorTokenClient; const externalServiceConfig = snExternalServiceConfig[actionTypeId]; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/mocks.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/mocks.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/schema.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/schema.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service.test.ts similarity index 99% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service.test.ts index 262569548fe94..28ee0e248c2b8 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service.test.ts @@ -8,18 +8,18 @@ import axios, { AxiosResponse } from 'axios'; import { createExternalService } from './service'; -import * as utils from '../../lib/axios_utils'; +import * as utils from '@kbn/actions-plugin/server/lib/axios_utils'; import { ExternalService, ServiceNowITSMIncident } from './types'; import { Logger } from '@kbn/core/server'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { actionsConfigMock } from '../../actions_config.mock'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; import { serviceNowCommonFields, serviceNowChoices } from './mocks'; import { snExternalServiceConfig } from './config'; const logger = loggingSystemMock.create().get() as jest.Mocked; jest.mock('axios'); -jest.mock('../../lib/axios_utils', () => { - const originalUtils = jest.requireActual('../../lib/axios_utils'); +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(), diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service.ts similarity index 99% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service.ts index f4a23ee224781..3f1b7cd7cdc64 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service.ts @@ -7,6 +7,7 @@ import { AxiosResponse } from 'axios'; +import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; import { ExternalService, ExternalServiceParamsCreate, @@ -20,7 +21,6 @@ import { import * as i18n from './translations'; import { ServiceNowPublicConfigurationType, ServiceNowSecretConfigurationType } from './types'; -import { request } from '../../lib/axios_utils'; import { createServiceError, getPushedDate, prepareIncident } from './utils'; export const SYS_DICTIONARY_ENDPOINT = `api/now/table/sys_dictionary`; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service_itom.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service_itom.test.ts similarity index 89% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/service_itom.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service_itom.test.ts index 43498f60b5196..ddc347f49dc76 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service_itom.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service_itom.test.ts @@ -8,19 +8,19 @@ import axios from 'axios'; import { createExternalServiceITOM } from './service_itom'; -import * as utils from '../../lib/axios_utils'; +import * as utils from '@kbn/actions-plugin/server/lib/axios_utils'; import { ExternalServiceITOM } from './types'; import { Logger } from '@kbn/core/server'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { actionsConfigMock } from '../../actions_config.mock'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; import { snExternalServiceConfig } from './config'; import { itomEventParams, serviceNowChoices } from './mocks'; const logger = loggingSystemMock.create().get() as jest.Mocked; jest.mock('axios'); -jest.mock('../../lib/axios_utils', () => { - const originalUtils = jest.requireActual('../../lib/axios_utils'); +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(), diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service_itom.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service_itom.ts similarity index 95% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/service_itom.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service_itom.ts index ac63dbd35e2b0..74e76e4aedc1d 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service_itom.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service_itom.ts @@ -5,9 +5,9 @@ * 2.0. */ +import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; import { ServiceFactory, ExternalServiceITOM, ExecutorSubActionAddEventParams } from './types'; -import { request } from '../../lib/axios_utils'; import { createExternalService } from './service'; import { createServiceError } from './utils'; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service_sir.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service_sir.test.ts similarity index 91% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/service_sir.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service_sir.test.ts index bed12a5970e5c..0cf30d358b47f 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service_sir.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service_sir.test.ts @@ -8,19 +8,19 @@ import axios from 'axios'; import { createExternalServiceSIR } from './service_sir'; -import * as utils from '../../lib/axios_utils'; +import * as utils from '@kbn/actions-plugin/server/lib/axios_utils'; import { ExternalServiceSIR } from './types'; import { Logger } from '@kbn/core/server'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { actionsConfigMock } from '../../actions_config.mock'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; import { observables } from './mocks'; import { snExternalServiceConfig } from './config'; const logger = loggingSystemMock.create().get() as jest.Mocked; jest.mock('axios'); -jest.mock('../../lib/axios_utils', () => { - const originalUtils = jest.requireActual('../../lib/axios_utils'); +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(), diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service_sir.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service_sir.ts similarity index 97% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/service_sir.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service_sir.ts index 796eff15806e2..de3220c36bd4c 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service_sir.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service_sir.ts @@ -5,9 +5,9 @@ * 2.0. */ +import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; import { Observable, ExternalServiceSIR, ObservableResponse, ServiceFactory } from './types'; -import { request } from '../../lib/axios_utils'; import { createExternalService } from './service'; import { createServiceError } from './utils'; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/translations.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/translations.ts similarity index 56% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/translations.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/translations.ts index b007e57773989..2a7e9f94c6704 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/translations.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/translations.ts @@ -7,24 +7,24 @@ import { i18n } from '@kbn/i18n'; -export const SERVICENOW = i18n.translate('xpack.actions.builtin.serviceNowTitle', { +export const SERVICENOW = i18n.translate('xpack.stackConnectors.serviceNow.title', { defaultMessage: 'ServiceNow', }); -export const SERVICENOW_ITSM = i18n.translate('xpack.actions.builtin.serviceNowITSMTitle', { +export const SERVICENOW_ITSM = i18n.translate('xpack.stackConnectors.serviceNowITSM.title', { defaultMessage: 'ServiceNow ITSM', }); -export const SERVICENOW_SIR = i18n.translate('xpack.actions.builtin.serviceNowSIRTitle', { +export const SERVICENOW_SIR = i18n.translate('xpack.stackConnectors.serviceNowSIR.title', { defaultMessage: 'ServiceNow SecOps', }); -export const SERVICENOW_ITOM = i18n.translate('xpack.actions.builtin.serviceNowITOMTitle', { +export const SERVICENOW_ITOM = i18n.translate('xpack.stackConnectors.serviceNowITOM.title', { defaultMessage: 'ServiceNow ITOM', }); export const ALLOWED_HOSTS_ERROR = (message: string) => - i18n.translate('xpack.actions.builtin.configuration.apiAllowedHostsError', { + i18n.translate('xpack.stackConnectors.serviceNow.configuration.apiAllowedHostsError', { defaultMessage: 'error configuring connector action: {message}', values: { message, @@ -32,37 +32,40 @@ export const ALLOWED_HOSTS_ERROR = (message: string) => }); export const CREDENTIALS_ERROR = i18n.translate( - 'xpack.actions.builtin.configuration.apiCredentialsError', + 'xpack.stackConnectors.serviceNow.configuration.apiCredentialsError', { defaultMessage: 'Either basic auth or OAuth credentials must be specified', } ); export const BASIC_AUTH_CREDENTIALS_ERROR = i18n.translate( - 'xpack.actions.builtin.configuration.apiBasicAuthCredentialsError', + 'xpack.stackConnectors.serviceNow.configuration.apiBasicAuthCredentialsError', { defaultMessage: 'username and password must both be specified', } ); export const OAUTH_CREDENTIALS_ERROR = i18n.translate( - 'xpack.actions.builtin.configuration.apiOAuthCredentialsError', + 'xpack.stackConnectors.serviceNow.configuration.apiOAuthCredentialsError', { defaultMessage: 'clientSecret and privateKey must both be specified', } ); export const VALIDATE_OAUTH_MISSING_FIELD_ERROR = (field: string, isOAuth: boolean) => - i18n.translate('xpack.actions.builtin.configuration.apiValidateMissingOAuthFieldError', { - defaultMessage: '{field} must be provided when isOAuth = {isOAuth}', - values: { - field, - isOAuth: isOAuth ? 'true' : 'false', - }, - }); + i18n.translate( + 'xpack.stackConnectors.serviceNow.configuration.apiValidateMissingOAuthFieldError', + { + defaultMessage: '{field} must be provided when isOAuth = {isOAuth}', + values: { + field, + isOAuth: isOAuth ? 'true' : 'false', + }, + } + ); export const VALIDATE_OAUTH_POPULATED_FIELD_ERROR = (field: string, isOAuth: boolean) => - i18n.translate('xpack.actions.builtin.configuration.apiValidateOAuthFieldError', { + i18n.translate('xpack.stackConnectors.serviceNow.configuration.apiValidateOAuthFieldError', { defaultMessage: '{field} should not be provided with isOAuth = {isOAuth}', values: { field, diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/types.ts similarity index 97% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/types.ts index 069f123fae4ec..dbd17e67d9500 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/types.ts @@ -10,6 +10,8 @@ import { AxiosError, AxiosInstance, AxiosResponse } from 'axios'; import { TypeOf } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { ValidatorServices } from '@kbn/actions-plugin/server/types'; import { ExecutorParamsSchemaITSM, ExecutorSubActionCommonFieldsParamsSchema, @@ -25,11 +27,9 @@ import { ExecutorSubActionAddEventParamsSchema, ExternalIncidentServiceConfigurationBaseSchema, } from './schema'; -import { ActionsConfigurationUtilities } from '../../actions_config'; -import { SNProductsConfigValue } from '../../../common'; -import { ValidatorServices } from '../../types'; +import { SNProductsConfigValue } from '../../../../common/servicenow_config'; -export type { SNProductsConfigValue, SNProductsConfig } from '../../../common'; +export type { SNProductsConfigValue, SNProductsConfig } from '../../../../common/servicenow_config'; export type ServiceNowPublicConfigurationBaseType = TypeOf< typeof ExternalIncidentServiceConfigurationBaseSchema diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/utils.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/utils.test.ts similarity index 97% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/utils.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/utils.test.ts index fcd2023dc8e27..56e6e45cd04c2 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/utils.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/utils.test.ts @@ -17,11 +17,11 @@ import { getAxiosInstance, } from './utils'; import type { ResponseError } from './types'; -import { connectorTokenClientMock } from '../lib/connector_token_client.mock'; -import { actionsConfigMock } from '../../actions_config.mock'; -import { getOAuthJwtAccessToken } from '../lib/get_oauth_jwt_access_token'; +import { connectorTokenClientMock } from '@kbn/actions-plugin/server/lib/connector_token_client.mock'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { getOAuthJwtAccessToken } from '@kbn/actions-plugin/server/lib/get_oauth_jwt_access_token'; -jest.mock('../lib/get_oauth_jwt_access_token', () => ({ +jest.mock('@kbn/actions-plugin/server/lib/get_oauth_jwt_access_token', () => ({ getOAuthJwtAccessToken: jest.fn(), })); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/utils.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/utils.ts similarity index 93% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/utils.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/utils.ts index 65c1cd8a5b1ac..f5ae7baa3e256 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/utils.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/utils.ts @@ -7,6 +7,10 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; import { Logger } from '@kbn/core/server'; +import { addTimeZoneToDate, getErrorMessage } from '@kbn/actions-plugin/server/lib/axios_utils'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { ConnectorTokenClientContract } from '@kbn/actions-plugin/server/types'; +import { getOAuthJwtAccessToken } from '@kbn/actions-plugin/server/lib/get_oauth_jwt_access_token'; import { ExternalServiceCredentials, Incident, @@ -17,11 +21,7 @@ import { ServiceNowSecretConfigurationType, } from './types'; import { FIELD_PREFIX } from './config'; -import { addTimeZoneToDate, getErrorMessage } from '../../lib/axios_utils'; import * as i18n from './translations'; -import { ActionsConfigurationUtilities } from '../../actions_config'; -import { ConnectorTokenClientContract } from '../../types'; -import { getOAuthJwtAccessToken } from '../lib/get_oauth_jwt_access_token'; export const prepareIncident = (useOldApi: boolean, incident: PartialIncident): PartialIncident => useOldApi diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/validators.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/validators.test.ts similarity index 99% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/validators.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/validators.test.ts index 8b2f6b26e1a5a..598f5ba576d0f 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/validators.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/validators.test.ts @@ -6,7 +6,7 @@ */ import { validateCommonConfig, validateCommonSecrets, validateCommonConnector } from './validators'; -import { actionsConfigMock } from '../../actions_config.mock'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; const configurationUtilities = actionsConfigMock.create(); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/validators.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/validators.ts similarity index 98% rename from x-pack/plugins/actions/server/builtin_action_types/servicenow/validators.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/validators.ts index 6978a5335f8b0..1ff9d09ad54ee 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/validators.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/validators.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ValidatorServices } from '@kbn/actions-plugin/server/types'; import { ServiceNowPublicConfigurationType, ServiceNowSecretConfigurationType, @@ -12,7 +13,6 @@ import { } from './types'; import * as i18n from './translations'; -import { ValidatorServices } from '../../types'; export const validateCommonConfig = ( config: ServiceNowPublicConfigurationType, diff --git a/x-pack/plugins/actions/server/builtin_action_types/swimlane/api.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/api.test.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/swimlane/api.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/api.test.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/swimlane/api.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/api.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/swimlane/api.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/api.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/swimlane/helpers.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/helpers.test.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/swimlane/helpers.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/helpers.test.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/swimlane/helpers.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/helpers.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/swimlane/helpers.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/helpers.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/swimlane/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/index.ts similarity index 76% rename from x-pack/plugins/actions/server/builtin_action_types/swimlane/index.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/index.ts index 38cf28edc1e09..a033abd875ea4 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/swimlane/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/index.ts @@ -8,8 +8,16 @@ import { curry } from 'lodash'; import { i18n } from '@kbn/i18n'; import { Logger } from '@kbn/logging'; -import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../../types'; -import { ActionsConfigurationUtilities } from '../../actions_config'; +import type { + ActionType as ConnectorType, + ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, + ActionTypeExecutorResult as ConnectorTypeExecutorResult, +} from '@kbn/actions-plugin/server/types'; +import { + AlertingConnectorFeatureId, + CasesConnectorFeatureId, + SecurityConnectorFeatureId, +} from '@kbn/actions-plugin/common/types'; import { SwimlaneExecutorResultData, SwimlanePublicConfigurationType, @@ -25,33 +33,28 @@ import { } from './schema'; import { createExternalService } from './service'; import { api } from './api'; -import { - AlertingConnectorFeatureId, - CasesConnectorFeatureId, - SecurityConnectorFeatureId, -} from '../../../common'; -interface GetActionTypeParams { + +interface GetConnectorTypeParams { logger: Logger; - configurationUtilities: ActionsConfigurationUtilities; } const supportedSubActions: string[] = ['pushToService']; -// action type definition -export function getActionType( - params: GetActionTypeParams -): ActionType< +// connector type definition +export function getConnectorType( + params: GetConnectorTypeParams +): ConnectorType< SwimlanePublicConfigurationType, SwimlaneSecretConfigurationType, ExecutorParams, SwimlaneExecutorResultData | {} > { - const { logger, configurationUtilities } = params; + const { logger } = params; return { id: '.swimlane', minimumLicenseRequired: 'gold', - name: i18n.translate('xpack.actions.builtin.swimlaneTitle', { + name: i18n.translate('xpack.stackConnectors.swimlane.title', { defaultMessage: 'Swimlane', }), supportedFeatureIds: [ @@ -72,22 +75,19 @@ export function getActionType( schema: ExecutorParamsSchema, }, }, - executor: curry(executor)({ logger, configurationUtilities }), + executor: curry(executor)({ logger }), }; } async function executor( - { - logger, - configurationUtilities, - }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, - execOptions: ActionTypeExecutorOptions< + { logger }: { logger: Logger }, + execOptions: ConnectorTypeExecutorOptions< SwimlanePublicConfigurationType, SwimlaneSecretConfigurationType, ExecutorParams > -): Promise> { - const { actionId, config, params, secrets } = execOptions; +): Promise> { + const { actionId, config, params, secrets, configurationUtilities } = execOptions; const { subAction, subActionParams } = params as ExecutorParams; let data: SwimlaneExecutorResultData | null = null; diff --git a/x-pack/plugins/actions/server/builtin_action_types/swimlane/mocks.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/mocks.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/swimlane/mocks.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/mocks.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/swimlane/schema.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/schema.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/swimlane/schema.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/schema.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/swimlane/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/service.test.ts similarity index 97% rename from x-pack/plugins/actions/server/builtin_action_types/swimlane/service.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/service.test.ts index c2b5a41ca7e95..1aeee9c586fd5 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/swimlane/service.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/service.test.ts @@ -9,8 +9,8 @@ import axios from 'axios'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { Logger } from '@kbn/core/server'; -import { actionsConfigMock } from '../../actions_config.mock'; -import { request, createAxiosResponse } from '../../lib/axios_utils'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { request, createAxiosResponse } from '@kbn/actions-plugin/server/lib/axios_utils'; import { createExternalService } from './service'; import { mappings } from './mocks'; import { ExternalService } from './types'; @@ -18,8 +18,8 @@ import { ExternalService } from './types'; const logger = loggingSystemMock.create().get() as jest.Mocked; jest.mock('axios'); -jest.mock('../../lib/axios_utils', () => { - const originalUtils = jest.requireActual('../../lib/axios_utils'); +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(), diff --git a/x-pack/plugins/actions/server/builtin_action_types/swimlane/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/service.ts similarity index 96% rename from x-pack/plugins/actions/server/builtin_action_types/swimlane/service.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/service.ts index 746cf25b63788..42c4b65408f21 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/swimlane/service.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/service.ts @@ -8,8 +8,12 @@ import { Logger } from '@kbn/logging'; import axios from 'axios'; -import { ActionsConfigurationUtilities } from '../../actions_config'; -import { getErrorMessage, request, throwIfResponseIsNotValid } from '../../lib/axios_utils'; +import { + getErrorMessage, + request, + throwIfResponseIsNotValid, +} from '@kbn/actions-plugin/server/lib/axios_utils'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; import { getBodyForEventAction } from './helpers'; import { CreateCommentParams, diff --git a/x-pack/plugins/actions/server/builtin_action_types/swimlane/translations.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/translations.ts similarity index 78% rename from x-pack/plugins/actions/server/builtin_action_types/swimlane/translations.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/translations.ts index 671cf224448f6..d820a3cfa57ae 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/swimlane/translations.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/translations.ts @@ -7,12 +7,12 @@ import { i18n } from '@kbn/i18n'; -export const NAME = i18n.translate('xpack.actions.builtin.case.swimlaneTitle', { +export const NAME = i18n.translate('xpack.stackConnectors.swimlane.title', { defaultMessage: 'Swimlane', }); export const ALLOWED_HOSTS_ERROR = (message: string) => - i18n.translate('xpack.actions.builtin.swimlane.configuration.apiAllowedHostsError', { + i18n.translate('xpack.stackConnectors.swimlane.configuration.apiAllowedHostsError', { defaultMessage: 'error configuring connector action: {message}', values: { message, diff --git a/x-pack/plugins/actions/server/builtin_action_types/swimlane/types.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/types.ts similarity index 98% rename from x-pack/plugins/actions/server/builtin_action_types/swimlane/types.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/types.ts index b00f0d25ee2f7..dc36aa13f6541 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/swimlane/types.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/types.ts @@ -8,6 +8,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { TypeOf } from '@kbn/config-schema'; import { Logger } from '@kbn/logging'; +import { ValidatorServices } from '@kbn/actions-plugin/server/types'; import { ConfigMappingSchema, ExecutorParamsSchema, @@ -15,7 +16,6 @@ import { SwimlaneSecretsConfigurationSchema, SwimlaneServiceConfigurationSchema, } from './schema'; -import { ValidatorServices } from '../../types'; export type SwimlanePublicConfigurationType = TypeOf; export type SwimlaneSecretConfigurationType = TypeOf; diff --git a/x-pack/plugins/actions/server/builtin_action_types/swimlane/validators.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/validators.ts similarity index 93% rename from x-pack/plugins/actions/server/builtin_action_types/swimlane/validators.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/validators.ts index fc1d0a95d0d85..050a25c5c9966 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/swimlane/validators.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/swimlane/validators.ts @@ -5,13 +5,13 @@ * 2.0. */ +import { ValidatorServices } from '@kbn/actions-plugin/server/types'; import { ExternalServiceValidation, SwimlanePublicConfigurationType, SwimlaneSecretConfigurationType, } from './types'; import * as i18n from './translations'; -import { ValidatorServices } from '../../types'; export const validateCommonConfig = ( configObject: SwimlanePublicConfigurationType, diff --git a/x-pack/plugins/actions/server/builtin_action_types/xmatters.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/xmatters/index.test.ts similarity index 80% rename from x-pack/plugins/actions/server/builtin_action_types/xmatters.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/xmatters/index.test.ts index 83e43ee8b3ba2..98cac875e6b55 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/xmatters.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/xmatters/index.test.ts @@ -5,58 +5,48 @@ * 2.0. */ -jest.mock('./lib/post_xmatters', () => ({ +jest.mock('./post_xmatters', () => ({ postXmatters: jest.fn(), })); +import { postXmatters } from './post_xmatters'; -import { Services } from '../types'; -import { validateConfig, validateSecrets, validateParams, validateConnector } from '../lib'; -import { postXmatters } from './lib/post_xmatters'; -import { actionsConfigMock } from '../actions_config.mock'; -import { createActionTypeRegistry } from './index.test'; import { Logger } from '@kbn/core/server'; -import { actionsMock } from '../mocks'; import { - ActionParamsType, - ActionTypeConfigType, - ActionTypeSecretsType, - getActionType, - XmattersActionType, -} from './xmatters'; -import { ActionsConfigurationUtilities } from '../actions_config'; + ConnectorTypeConfigType, + ConnectorTypeSecretsType, + getConnectorType, + XmattersConnectorType, +} from '.'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; +import { Services } from '@kbn/actions-plugin/server/types'; +import { + validateConfig, + validateConnector, + validateParams, + validateSecrets, +} from '@kbn/actions-plugin/server/lib'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { loggerMock } from '@kbn/logging-mocks'; const postxMattersMock = postXmatters as jest.Mock; - -const ACTION_TYPE_ID = '.xmatters'; - const services: Services = actionsMock.createServices(); +const mockedLogger: jest.Mocked = loggerMock.create(); -let actionType: XmattersActionType; -let mockedLogger: jest.Mocked; +let connectorType: XmattersConnectorType; let configurationUtilities: jest.Mocked; -beforeAll(() => { - const { logger, actionTypeRegistry } = createActionTypeRegistry(); - actionType = actionTypeRegistry.get< - ActionTypeConfigType, - ActionTypeSecretsType, - ActionParamsType - >(ACTION_TYPE_ID); - mockedLogger = logger; -}); - beforeEach(() => { configurationUtilities = actionsConfigMock.create(); - actionType = getActionType({ + connectorType = getConnectorType({ logger: mockedLogger, - configurationUtilities, }); }); -describe('actionType', () => { - test('exposes the action as `xmatters` on its Id and Name', () => { - expect(actionType.id).toEqual('.xmatters'); - expect(actionType.name).toEqual('xMatters'); +describe('connectorType', () => { + test('exposes the connector as `xmatters` on its Id and Name', () => { + expect(connectorType.id).toEqual('.xmatters'); + expect(connectorType.name).toEqual('xMatters'); }); }); @@ -66,7 +56,7 @@ describe('secrets validation', () => { user: 'bob', password: 'supersecret', }; - expect(validateSecrets(actionType, secrets, { configurationUtilities })).toEqual({ + expect(validateSecrets(connectorType, secrets, { configurationUtilities })).toEqual({ ...secrets, secretsUrl: null, }); @@ -76,7 +66,7 @@ describe('secrets validation', () => { const secrets: Record = { secretsUrl: 'http://mylisteningserver:9200/endpoint?apiKey=someKey', }; - expect(validateSecrets(actionType, secrets, { configurationUtilities })).toEqual({ + expect(validateSecrets(connectorType, secrets, { configurationUtilities })).toEqual({ ...secrets, user: null, password: null, @@ -89,7 +79,7 @@ describe('secrets validation', () => { secretsUrl: 'http://mylisteningserver:9200/endpoint?apiKey=someKey', }; expect(() => { - validateSecrets(actionType, secrets, { configurationUtilities }); + validateSecrets(connectorType, secrets, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: Cannot use user/password for URL authentication. Provide valid secretsUrl or use Basic Authentication."` ); @@ -101,7 +91,7 @@ describe('secrets validation', () => { secretsUrl: 'http://mylisteningserver:9200/endpoint?apiKey=someKey', }; expect(() => { - validateSecrets(actionType, secrets, { configurationUtilities }); + validateSecrets(connectorType, secrets, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: Cannot use user/password for URL authentication. Provide valid secretsUrl or use Basic Authentication."` ); @@ -114,7 +104,7 @@ describe('secrets validation', () => { secretsUrl: 'http://mylisteningserver:9200/endpoint?apiKey=someKey', }; expect(() => { - validateSecrets(actionType, secrets, { configurationUtilities }); + validateSecrets(connectorType, secrets, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: Cannot use user/password for URL authentication. Provide valid secretsUrl or use Basic Authentication."` ); @@ -122,7 +112,7 @@ describe('secrets validation', () => { test('fails when secret user is provided, but password is omitted', () => { expect(() => { - validateSecrets(actionType, { user: 'bob' }, { configurationUtilities }); + validateSecrets(connectorType, { user: 'bob' }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: Both user and password must be specified."` ); @@ -130,7 +120,7 @@ describe('secrets validation', () => { test('fails when password is provided, but user is omitted', () => { expect(() => { - validateSecrets(actionType, { password: 'supersecret' }, { configurationUtilities }); + validateSecrets(connectorType, { password: 'supersecret' }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: Both user and password must be specified."` ); @@ -138,7 +128,7 @@ describe('secrets validation', () => { test('fails when user, password, and secretsUrl are omitted', () => { expect(() => { - validateSecrets(actionType, {}, { configurationUtilities }); + validateSecrets(connectorType, {}, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: Provide either secretsUrl link or user/password to authenticate"` ); @@ -149,7 +139,7 @@ describe('secrets validation', () => { secretsUrl: 'example.com/do-something?apiKey=someKey', }; expect(() => { - validateSecrets(actionType, secrets, { configurationUtilities }); + validateSecrets(connectorType, secrets, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( '"error validating action type secrets: Invalid secretsUrl: TypeError: Invalid URL: example.com/do-something?apiKey=someKey"' ); @@ -162,16 +152,12 @@ describe('secrets validation', () => { throw new Error(`target url is not present in allowedHosts`); }, }; - actionType = getActionType({ - logger: mockedLogger, - configurationUtilities: configUtils, - }); const secrets: Record = { secretsUrl: 'http://mylisteningserver.com:9200/endpoint', }; expect(() => { - validateSecrets(actionType, secrets, { configurationUtilities: configUtils }); + validateSecrets(connectorType, secrets, { configurationUtilities: configUtils }); }).toThrowErrorMatchingInlineSnapshot( '"error validating action type secrets: target url is not present in allowedHosts"' ); @@ -184,7 +170,7 @@ describe('config validation', () => { configUrl: 'http://mylisteningserver:9200/endpoint', usesBasic: true, }; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual(config); + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual(config); }); test('config validation failed when a url is invalid', () => { @@ -193,7 +179,7 @@ describe('config validation', () => { usesBasic: true, }; expect(() => { - validateConfig(actionType, config, { configurationUtilities }); + validateConfig(connectorType, config, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( '"error validating action type config: Error configuring xMatters action: unable to parse url: TypeError: Invalid URL: example.com/do-something"' ); @@ -206,18 +192,13 @@ describe('config validation', () => { throw new Error(`target url is not present in allowedHosts`); }, }; - actionType = getActionType({ - logger: mockedLogger, - configurationUtilities: configUtils, - }); - const config: Record = { configUrl: 'http://mylisteningserver.com:9200/endpoint', usesBasic: true, }; expect(() => { - validateConfig(actionType, config, { configurationUtilities: configUtils }); + validateConfig(connectorType, config, { configurationUtilities: configUtils }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type config: Error configuring xMatters action: target url is not present in allowedHosts"` ); @@ -229,7 +210,7 @@ describe('config validation', () => { usesBasic: false, }; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual(config); + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual(config); }); }); @@ -238,7 +219,7 @@ describe('params validation', () => { const params: Record = { severity: 'high', }; - expect(validateParams(actionType, params, { configurationUtilities })).toEqual({ + expect(validateParams(connectorType, params, { configurationUtilities })).toEqual({ severity: 'high', }); }); @@ -253,7 +234,7 @@ describe('params validation', () => { spaceId: 'default', tags: 'test1, test2', }; - expect(validateParams(actionType, params, { configurationUtilities })).toEqual({ + expect(validateParams(connectorType, params, { configurationUtilities })).toEqual({ ...params, }); }); @@ -267,7 +248,7 @@ describe('connector validation', () => { }; const secrets: Record = {}; expect(() => { - validateConnector(actionType, { config, secrets }); + validateConnector(connectorType, { config, secrets }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type connector: Provide valid Username"` ); @@ -282,7 +263,7 @@ describe('connector validation', () => { user: 'bob', }; expect(() => { - validateConnector(actionType, { config, secrets }); + validateConnector(connectorType, { config, secrets }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type connector: Provide valid Password"` ); @@ -297,7 +278,7 @@ describe('connector validation', () => { password: 'supersecret', }; expect(() => { - validateConnector(actionType, { config, secrets }); + validateConnector(connectorType, { config, secrets }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type connector: Provide valid configUrl"` ); @@ -313,7 +294,7 @@ describe('connector validation', () => { secretsUrl: 'http://mylisteningserver:9200/endpoint', }; expect(() => { - validateConnector(actionType, { config, secrets }); + validateConnector(connectorType, { config, secrets }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type connector: Username and password should not be provided when usesBasic is false"` ); @@ -330,7 +311,7 @@ describe('connector validation', () => { secretsUrl: 'http://mylisteningserver:9200/endpoint', }; expect(() => { - validateConnector(actionType, { config, secrets }); + validateConnector(connectorType, { config, secrets }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type connector: secretsUrl should not be provided when usesBasic is true"` ); @@ -344,7 +325,7 @@ describe('connector validation', () => { secretsUrl: 'http://mylisteningserver:9200/endpoint', }; expect(() => { - validateConnector(actionType, { config, secrets }); + validateConnector(connectorType, { config, secrets }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type connector: secretsUrl should not be provided when usesBasic is true"` ); @@ -360,7 +341,7 @@ describe('connector validation', () => { password: 'supersecret', }; expect(() => { - validateConnector(actionType, { config, secrets }); + validateConnector(connectorType, { config, secrets }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type connector: Username and password should not be provided when usesBasic is false"` ); @@ -373,7 +354,7 @@ describe('connector validation', () => { }; const secrets: Record = {}; expect(() => { - validateConnector(actionType, { config, secrets }); + validateConnector(connectorType, { config, secrets }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type connector: configUrl should not be provided when usesBasic is false"` ); @@ -388,7 +369,7 @@ describe('connector validation', () => { user: 'bob', password: 'supersecret', }; - expect(validateConnector(actionType, { config, secrets })).toEqual(null); + expect(validateConnector(connectorType, { config, secrets })).toEqual(null); }); test('connector validation succeeds with url auth', () => { @@ -398,7 +379,7 @@ describe('connector validation', () => { const secrets: Record = { secretsUrl: 'http://mylisteningserver:9200/endpoint', }; - expect(validateConnector(actionType, { config, secrets })).toEqual(null); + expect(validateConnector(connectorType, { config, secrets })).toEqual(null); }); }); @@ -414,11 +395,11 @@ describe('execute()', () => { }); test('execute with useBasic=true uses authentication object', async () => { - const config: ActionTypeConfigType = { + const config: ConnectorTypeConfigType = { configUrl: 'https://abc.def/my-xmatters', usesBasic: true, }; - await actionType.executor({ + await connectorType.executor({ actionId: 'some-id', services, config, @@ -432,6 +413,7 @@ describe('execute()', () => { spaceId: 'default', tags: 'test1, test2', }, + configurationUtilities, }); expect(postxMattersMock.mock.calls[0][0]).toMatchInlineSnapshot(` @@ -457,7 +439,7 @@ describe('execute()', () => { }); test('execute with exception maxContentLength size exceeded should log the proper error', async () => { - const config: ActionTypeConfigType = { + const config: ConnectorTypeConfigType = { configUrl: 'https://abc.def/my-xmatters', usesBasic: true, }; @@ -465,7 +447,7 @@ describe('execute()', () => { tag: 'err', message: 'maxContentLength size of 1000000 exceeded', }); - await actionType.executor({ + await connectorType.executor({ actionId: 'some-id', services, config, @@ -479,6 +461,7 @@ describe('execute()', () => { spaceId: 'default', tags: 'test1, test2', }, + configurationUtilities, }); expect(mockedLogger.warn).toBeCalledWith( 'Error thrown triggering xMatters workflow: maxContentLength size of 1000000 exceeded' @@ -486,16 +469,16 @@ describe('execute()', () => { }); test('execute with useBasic=false uses empty authentication object', async () => { - const config: ActionTypeConfigType = { + const config: ConnectorTypeConfigType = { configUrl: null, usesBasic: false, }; - const secrets: ActionTypeSecretsType = { + const secrets: ConnectorTypeSecretsType = { user: null, password: null, secretsUrl: 'https://abc.def/my-xmatters?apiKey=someKey', }; - await actionType.executor({ + await connectorType.executor({ actionId: 'some-id', services, config, @@ -509,6 +492,7 @@ describe('execute()', () => { spaceId: 'default', tags: 'test1, test2', }, + configurationUtilities, }); expect(postxMattersMock.mock.calls[0][0]).toMatchInlineSnapshot(` diff --git a/x-pack/plugins/actions/server/builtin_action_types/xmatters.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/xmatters/index.ts similarity index 72% rename from x-pack/plugins/actions/server/builtin_action_types/xmatters.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/xmatters/index.ts index 8bd6350ada633..0686d37a953f5 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/xmatters.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/xmatters/index.ts @@ -5,29 +5,28 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; import { curry, isString } from 'lodash'; +import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; -import { - ActionType, - ActionTypeExecutorOptions, - ActionTypeExecutorResult, +import type { + ActionType as ConnectorType, + ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, + ActionTypeExecutorResult as ConnectorTypeExecutorResult, ValidatorServices, -} from '../types'; -import { ActionsConfigurationUtilities } from '../actions_config'; -import { postXmatters } from './lib/post_xmatters'; -import { AlertingConnectorFeatureId } from '../../common'; +} from '@kbn/actions-plugin/server/types'; +import { AlertingConnectorFeatureId } from '@kbn/actions-plugin/common/types'; +import { postXmatters } from './post_xmatters'; -export type XmattersActionType = ActionType< - ActionTypeConfigType, - ActionTypeSecretsType, +export type XmattersConnectorType = ConnectorType< + ConnectorTypeConfigType, + ConnectorTypeSecretsType, ActionParamsType, unknown >; -export type XmattersActionTypeExecutorOptions = ActionTypeExecutorOptions< - ActionTypeConfigType, - ActionTypeSecretsType, +export type XmattersConnectorTypeExecutorOptions = ConnectorTypeExecutorOptions< + ConnectorTypeConfigType, + ConnectorTypeSecretsType, ActionParamsType >; @@ -36,10 +35,10 @@ const configSchemaProps = { usesBasic: schema.boolean({ defaultValue: true }), }; const ConfigSchema = schema.object(configSchemaProps); -export type ActionTypeConfigType = TypeOf; +export type ConnectorTypeConfigType = TypeOf; // secrets definition -export type ActionTypeSecretsType = TypeOf; +export type ConnectorTypeSecretsType = TypeOf; const secretSchemaProps = { user: schema.nullable(schema.string()), password: schema.nullable(schema.string()), @@ -59,42 +58,36 @@ const ParamsSchema = schema.object({ tags: schema.maybe(schema.string()), }); -export const ActionTypeId = '.xmatters'; -// action type definition -export function getActionType({ - logger, - configurationUtilities, -}: { - logger: Logger; - configurationUtilities: ActionsConfigurationUtilities; -}): XmattersActionType { +export const ConnectorTypeId = '.xmatters'; +// connector type definition +export function getConnectorType({ logger }: { logger: Logger }): XmattersConnectorType { return { - id: ActionTypeId, + id: ConnectorTypeId, minimumLicenseRequired: 'gold', - name: i18n.translate('xpack.actions.builtin.xmattersTitle', { + name: i18n.translate('xpack.stackConnectors.xmatters.title', { defaultMessage: 'xMatters', }), supportedFeatureIds: [AlertingConnectorFeatureId], validate: { config: { schema: ConfigSchema, - customValidator: validateActionTypeConfig, + customValidator: validateConnectorTypeConfig, }, secrets: { schema: SecretsSchema, - customValidator: validateActionTypeSecrets, + customValidator: validateConnectorTypeSecrets, }, params: { schema: ParamsSchema, }, connector: validateConnector, }, - executor: curry(executor)({ logger, configurationUtilities }), + executor: curry(executor)({ logger }), }; } -function validateActionTypeConfig( - configObject: ActionTypeConfigType, +function validateConnectorTypeConfig( + configObject: ConnectorTypeConfigType, validatorServices: ValidatorServices ) { const { configurationUtilities } = validatorServices; @@ -107,7 +100,7 @@ function validateActionTypeConfig( } } catch (err) { throw new Error( - i18n.translate('xpack.actions.builtin.xmatters.xmattersConfigurationErrorNoHostname', { + i18n.translate('xpack.stackConnectors.xmatters.configurationErrorNoHostname', { defaultMessage: 'Error configuring xMatters action: unable to parse url: {err}', values: { err, @@ -122,7 +115,7 @@ function validateActionTypeConfig( } } catch (allowListError) { throw new Error( - i18n.translate('xpack.actions.builtin.xmatters.xmattersConfigurationError', { + i18n.translate('xpack.stackConnectors.xmatters.configurationError', { defaultMessage: 'Error configuring xMatters action: {message}', values: { message: allowListError.message, @@ -133,46 +126,46 @@ function validateActionTypeConfig( } function validateConnector( - config: ActionTypeConfigType, - secrets: ActionTypeSecretsType + config: ConnectorTypeConfigType, + secrets: ConnectorTypeSecretsType ): string | null { const { user, password, secretsUrl } = secrets; const { usesBasic, configUrl } = config; if (usesBasic) { if (secretsUrl) { - return i18n.translate('xpack.actions.builtin.xmatters.shouldNotHaveSecretsUrl', { + return i18n.translate('xpack.stackConnectors.xmatters.shouldNotHaveSecretsUrl', { defaultMessage: 'secretsUrl should not be provided when usesBasic is true', }); } if (user == null) { - return i18n.translate('xpack.actions.builtin.xmatters.missingUser', { + return i18n.translate('xpack.stackConnectors.xmatters.missingUser', { defaultMessage: 'Provide valid Username', }); } if (password == null) { - return i18n.translate('xpack.actions.builtin.xmatters.missingPassword', { + return i18n.translate('xpack.stackConnectors.xmatters.missingPassword', { defaultMessage: 'Provide valid Password', }); } if (configUrl == null) { - return i18n.translate('xpack.actions.builtin.xmatters.missingConfigUrl', { + return i18n.translate('xpack.stackConnectors.xmatters.missingConfigUrl', { defaultMessage: 'Provide valid configUrl', }); } } else { if (user || password) { - return i18n.translate('xpack.actions.builtin.xmatters.shouldNotHaveUsernamePassword', { + return i18n.translate('xpack.stackConnectors.xmatters.shouldNotHaveUsernamePassword', { defaultMessage: 'Username and password should not be provided when usesBasic is false', }); } if (configUrl) { - return i18n.translate('xpack.actions.builtin.xmatters.shouldNotHaveConfigUrl', { + return i18n.translate('xpack.stackConnectors.xmatters.shouldNotHaveConfigUrl', { defaultMessage: 'configUrl should not be provided when usesBasic is false', }); } if (secretsUrl == null) { - return i18n.translate('xpack.actions.builtin.xmatters.missingSecretsUrl', { + return i18n.translate('xpack.stackConnectors.xmatters.missingSecretsUrl', { defaultMessage: 'Provide valid secretsUrl with API Key', }); } @@ -180,14 +173,14 @@ function validateConnector( return null; } -function validateActionTypeSecrets( - secretsObject: ActionTypeSecretsType, +function validateConnectorTypeSecrets( + secretsObject: ConnectorTypeSecretsType, validatorServices: ValidatorServices ) { const { configurationUtilities } = validatorServices; if (!secretsObject.secretsUrl && !secretsObject.user && !secretsObject.password) { throw new Error( - i18n.translate('xpack.actions.builtin.xmatters.noSecretsProvided', { + i18n.translate('xpack.stackConnectors.xmatters.noSecretsProvided', { defaultMessage: 'Provide either secretsUrl link or user/password to authenticate', }) ); @@ -198,7 +191,7 @@ function validateActionTypeSecrets( // Neither user/password should be defined if secretsUrl is specified if (secretsObject.user || secretsObject.password) { throw new Error( - i18n.translate('xpack.actions.builtin.xmatters.noUserPassWhenSecretsUrl', { + i18n.translate('xpack.stackConnectors.xmatters.noUserPassWhenSecretsUrl', { defaultMessage: 'Cannot use user/password for URL authentication. Provide valid secretsUrl or use Basic Authentication.', }) @@ -212,7 +205,7 @@ function validateActionTypeSecrets( } } catch (err) { throw new Error( - i18n.translate('xpack.actions.builtin.xmatters.xmattersInvalidUrlError', { + i18n.translate('xpack.stackConnectors.xmatters.invalidUrlError', { defaultMessage: 'Invalid secretsUrl: {err}', values: { err, @@ -228,7 +221,7 @@ function validateActionTypeSecrets( } } catch (allowListError) { throw new Error( - i18n.translate('xpack.actions.builtin.xmatters.xmattersHostnameNotAllowed', { + i18n.translate('xpack.stackConnectors.xmatters.hostnameNotAllowed', { defaultMessage: '{message}', values: { message: allowListError.message, @@ -240,7 +233,7 @@ function validateActionTypeSecrets( // Username and password must both be set if (!secretsObject.user || !secretsObject.password) { throw new Error( - i18n.translate('xpack.actions.builtin.xmatters.invalidUsernamePassword', { + i18n.translate('xpack.stackConnectors.xmatters.invalidUsernamePassword', { defaultMessage: 'Both user and password must be specified.', }) ); @@ -250,17 +243,15 @@ function validateActionTypeSecrets( // action executor export async function executor( - { - logger, - configurationUtilities, - }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, - execOptions: XmattersActionTypeExecutorOptions -): Promise> { + { logger }: { logger: Logger }, + execOptions: XmattersConnectorTypeExecutorOptions +): Promise> { const actionId = execOptions.actionId; + const configurationUtilities = execOptions.configurationUtilities; const { configUrl, usesBasic } = execOptions.config; const data = getPayloadForRequest(execOptions.params); - const secrets: ActionTypeSecretsType = execOptions.secrets; + const secrets: ConnectorTypeSecretsType = execOptions.secrets; const basicAuth = usesBasic && isString(secrets.user) && isString(secrets.password) ? { auth: { username: secrets.user, password: secrets.password } } @@ -274,7 +265,7 @@ export async function executor( } result = await postXmatters({ url, data, basicAuth }, logger, configurationUtilities); } catch (err) { - const message = i18n.translate('xpack.actions.builtin.xmatters.postingErrorMessage', { + const message = i18n.translate('xpack.stackConnectors.xmatters.postingErrorMessage', { defaultMessage: 'Error triggering xMatters workflow', }); logger.warn(`Error thrown triggering xMatters workflow: ${err.message}`); @@ -294,7 +285,7 @@ export async function executor( } if (result.status === 429 || result.status >= 500) { - const message = i18n.translate('xpack.actions.builtin.xmatters.postingRetryErrorMessage', { + const message = i18n.translate('xpack.stackConnectors.xmatters.postingRetryErrorMessage', { defaultMessage: 'Error triggering xMatters flow: http status {status}, retry later', values: { status: result.status, @@ -308,7 +299,7 @@ export async function executor( retry: true, }; } - const message = i18n.translate('xpack.actions.builtin.xmatters.unexpectedStatusErrorMessage', { + const message = i18n.translate('xpack.stackConnectors.xmatters.unexpectedStatusErrorMessage', { defaultMessage: 'Error triggering xMatters flow: unexpected status {status}', values: { status: result.status, @@ -323,7 +314,7 @@ export async function executor( } // Action Executor Result w/ internationalisation -function successResult(actionId: string, data: unknown): ActionTypeExecutorResult { +function successResult(actionId: string, data: unknown): ConnectorTypeExecutorResult { return { status: 'ok', data, actionId }; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/post_xmatters.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/xmatters/post_xmatters.ts similarity index 87% rename from x-pack/plugins/actions/server/builtin_action_types/lib/post_xmatters.ts rename to x-pack/plugins/stack_connectors/server/connector_types/cases/xmatters/post_xmatters.ts index 67a7504a339e2..1fcec5a4238f7 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/post_xmatters.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/xmatters/post_xmatters.ts @@ -7,8 +7,8 @@ import axios, { AxiosResponse } from 'axios'; import { Logger } from '@kbn/core/server'; -import { request } from '../../lib/axios_utils'; -import { ActionsConfigurationUtilities } from '../../actions_config'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; interface PostXmattersOptions { url: string; 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 new file mode 100644 index 0000000000000..55fd99c040941 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/index.test.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { registerConnectorTypes } from '.'; +import { Logger } from '@kbn/core/server'; +import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; + +const ACTION_TYPE_IDS = [ + '.index', + '.email', + '.pagerduty', + '.server-log', + '.slack', + '.swimlane', + '.teams', + '.webhook', + '.xmatters', +]; + +const logger = loggingSystemMock.create().get() as jest.Mocked; +const mockedActions = actionsMock.createSetup(); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('registers connectors', () => { + test('calls registerType with expected connector types', () => { + registerConnectorTypes({ + logger, + actions: mockedActions, + }); + ACTION_TYPE_IDS.forEach((id) => + expect(mockedActions.registerType).toHaveBeenCalledWith( + expect.objectContaining({ + id, + }) + ) + ); + }); +}); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/index.ts new file mode 100644 index 0000000000000..db227eb96109a --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/index.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Logger } from '@kbn/core/server'; +import { PluginSetupContract as ActionsPluginSetupContract } from '@kbn/actions-plugin/server'; +import { + getEmailConnectorType, + getIndexConnectorType, + getPagerDutyConnectorType, + getServerLogConnectorType, + getSlackConnectorType, + getTeamsConnectorType, + getWebhookConnectorType, +} from './stack'; +import { + getCasesWebhookConnectorType, + getJiraConnectorType, + getResilientConnectorType, + getServiceNowITOMConnectorType, + getServiceNowITSMConnectorType, + getServiceNowSIRConnectorType, + getSwimlaneConnectorType, + getXmattersConnectorType, +} from './cases'; + +export type { + EmailActionParams, + IndexActionParams, + PagerDutyActionParams, + ServerLogActionParams, + SlackActionParams, + TeamsActionParams, + WebhookActionParams, +} from './stack'; +export { + EmailConnectorTypeId, + IndexConnectorTypeId, + PagerDutyConnectorTypeId, + ServerLogConnectorTypeId, + SlackConnectorTypeId, + TeamsConnectorTypeId, + WebhookConnectorTypeId, +} from './stack'; +export type { + CasesWebhookActionParams, + JiraActionParams, + ResilientActionParams, + ServiceNowActionParams, + XmattersActionParams, +} from './cases'; +export { + CasesWebhookConnectorTypeId, + JiraConnectorTypeId, + ResilientConnectorTypeId, + ServiceNowITOMConnectorTypeId, + ServiceNowITSMConnectorTypeId, + ServiceNowSIRConnectorTypeId, + XmattersConnectorTypeId, +} from './cases'; + +export function registerConnectorTypes({ + actions, + logger, + publicBaseUrl, +}: { + actions: ActionsPluginSetupContract; + logger: Logger; + publicBaseUrl?: string; +}) { + actions.registerType(getEmailConnectorType({ logger, publicBaseUrl })); + actions.registerType(getIndexConnectorType({ logger })); + actions.registerType(getPagerDutyConnectorType({ logger })); + actions.registerType(getSwimlaneConnectorType({ logger })); + actions.registerType(getServerLogConnectorType({ logger })); + actions.registerType(getSlackConnectorType({ logger })); + actions.registerType(getWebhookConnectorType({ logger })); + actions.registerType(getCasesWebhookConnectorType({ logger })); + actions.registerType(getXmattersConnectorType({ logger })); + actions.registerType(getServiceNowITSMConnectorType({ logger })); + actions.registerType(getServiceNowSIRConnectorType({ logger })); + actions.registerType(getServiceNowITOMConnectorType({ logger })); + actions.registerType(getJiraConnectorType({ logger })); + actions.registerType(getResilientConnectorType({ logger })); + actions.registerType(getTeamsConnectorType({ logger })); +} diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/http_rersponse_retry_header.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/http_response_retry_header.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/lib/http_rersponse_retry_header.ts rename to x-pack/plugins/stack_connectors/server/connector_types/lib/http_response_retry_header.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/nullable.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/nullable.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/lib/nullable.ts rename to x-pack/plugins/stack_connectors/server/connector_types/lib/nullable.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/result_type.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/result_type.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/lib/result_type.ts rename to x-pack/plugins/stack_connectors/server/connector_types/lib/result_type.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/schemas.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/schemas.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/lib/schemas.ts rename to x-pack/plugins/stack_connectors/server/connector_types/lib/schemas.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/string_utils.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/string_utils.test.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/lib/string_utils.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/lib/string_utils.test.ts diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/string_utils.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/string_utils.ts similarity index 100% rename from x-pack/plugins/actions/server/builtin_action_types/lib/string_utils.ts rename to x-pack/plugins/stack_connectors/server/connector_types/lib/string_utils.ts diff --git a/x-pack/plugins/stack_connectors/server/connector_types/security/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/security/index.ts new file mode 100644 index 0000000000000..1fec1c76430eb --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/security/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/email/index.test.ts similarity index 78% rename from x-pack/plugins/actions/server/builtin_action_types/email.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/email/index.test.ts index e586526d74e76..ab5f909ecd4f7 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/email/index.test.ts @@ -5,53 +5,52 @@ * 2.0. */ -jest.mock('./lib/send_email', () => ({ +jest.mock('./send_email', () => ({ sendEmail: jest.fn(), })); import { Logger } from '@kbn/core/server'; - -import { actionsConfigMock } from '../actions_config.mock'; -import { validateConfig, validateConnector, validateParams, validateSecrets } from '../lib'; -import { createActionTypeRegistry } from './index.test'; -import { sendEmail } from './lib/send_email'; -import { actionsMock } from '../mocks'; +import { loggerMock } from '@kbn/logging-mocks'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { + validateConfig, + validateConnector, + validateParams, + validateSecrets, +} from '@kbn/actions-plugin/server/lib'; +import { sendEmail } from './send_email'; import { ActionParamsType, - ActionTypeConfigType, - ActionTypeSecretsType, - getActionType, - EmailActionType, - EmailActionTypeExecutorOptions, -} from './email'; -import { ValidateEmailAddressesOptions } from '../../common'; -import { ActionsConfigurationUtilities } from '../actions_config'; + getConnectorType, + EmailConnectorType, + EmailConnectorTypeExecutorOptions, + ConnectorTypeConfigType, + ConnectorTypeSecretsType, +} from '.'; +import { ValidateEmailAddressesOptions } from '@kbn/actions-plugin/common'; const sendEmailMock = sendEmail as jest.Mock; -const ACTION_TYPE_ID = '.email'; - const services = actionsMock.createServices(); +const mockedLogger: jest.Mocked = loggerMock.create(); -let actionType: EmailActionType; -let mockedLogger: jest.Mocked; -let configurationUtilities: ActionsConfigurationUtilities; +let connectorType: EmailConnectorType; +let configurationUtilities: jest.Mocked; beforeEach(() => { jest.resetAllMocks(); - const { actionTypeRegistry } = createActionTypeRegistry(); - configurationUtilities = actionTypeRegistry.getUtils(); - actionType = actionTypeRegistry.get< - ActionTypeConfigType, - ActionTypeSecretsType, - ActionParamsType - >(ACTION_TYPE_ID); + configurationUtilities = actionsConfigMock.create(); + connectorType = getConnectorType({ + logger: mockedLogger, + }); }); -describe('actionTypeRegistry.get() works', () => { - test('action type static data is as expected', () => { - expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('Email'); +describe('connector registration', () => { + test('returns connector type', () => { + expect(connectorType.id).toEqual('.email'); + expect(connectorType.name).toEqual('Email'); }); }); @@ -62,7 +61,7 @@ describe('config validation', () => { from: 'bob@example.com', hasAuth: true, }; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ ...config, host: null, port: null, @@ -80,7 +79,7 @@ describe('config validation', () => { port: 8080, hasAuth: true, }; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ ...config, service: 'other', secure: null, @@ -98,7 +97,7 @@ describe('config validation', () => { port: 8080, hasAuth: true, }; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ ...config, secure: null, clientId: null, @@ -115,7 +114,7 @@ describe('config validation', () => { tenantId: '12345778', hasAuth: true, }; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ ...config, secure: null, host: null, @@ -130,7 +129,7 @@ describe('config validation', () => { from: 'bob@example.com', hasAuth: true, }; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ ...config, host: null, port: null, @@ -148,28 +147,32 @@ describe('config validation', () => { // empty object expect(() => { - validateConfig(actionType, {}, { configurationUtilities }); + validateConfig(connectorType, {}, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type config: [from]: expected value of type [string] but got [undefined]"` ); // no service or host/port expect(() => { - validateConfig(actionType, baseConfig, { configurationUtilities }); + validateConfig(connectorType, baseConfig, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type config: [host]/[port] is required"` ); // host but no port expect(() => { - validateConfig(actionType, { ...baseConfig, host: 'elastic.co' }, { configurationUtilities }); + validateConfig( + connectorType, + { ...baseConfig, host: 'elastic.co' }, + { configurationUtilities } + ); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type config: [port] is required"` ); // port but no host expect(() => { - validateConfig(actionType, { ...baseConfig, port: 8080 }, { configurationUtilities }); + validateConfig(connectorType, { ...baseConfig, port: 8080 }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type config: [host] is required"` ); @@ -177,7 +180,7 @@ describe('config validation', () => { // invalid service expect(() => { validateConfig( - actionType, + connectorType, { ...baseConfig, service: 'bad-nodemailer-service', @@ -191,7 +194,7 @@ describe('config validation', () => { // invalid exchange_server no clientId and no tenantId expect(() => { validateConfig( - actionType, + connectorType, { ...baseConfig, service: 'exchange_server', @@ -205,7 +208,7 @@ describe('config validation', () => { // invalid exchange_server no clientId expect(() => { validateConfig( - actionType, + connectorType, { ...baseConfig, service: 'exchange_server', @@ -220,7 +223,7 @@ describe('config validation', () => { // invalid exchange_server no tenantId expect(() => { validateConfig( - actionType, + connectorType, { ...baseConfig, service: 'exchange_server', @@ -242,10 +245,6 @@ describe('config validation', () => { ...actionsConfigMock.create(), isHostnameAllowed: (hostname: string) => hostname === NODEMAILER_AOL_SERVICE_HOST, }; - actionType = getActionType({ - logger: mockedLogger, - configurationUtilities: configUtils, - }); const baseConfig = { from: 'bob@example.com', }; @@ -269,13 +268,13 @@ describe('config validation', () => { port: 42, }; - const validatedConfig1 = validateConfig(actionType, allowedHosts1, { + const validatedConfig1 = validateConfig(connectorType, allowedHosts1, { configurationUtilities: configUtils, }); expect(validatedConfig1.service).toEqual(allowedHosts1.service); expect(validatedConfig1.from).toEqual(allowedHosts1.from); - const validatedConfig2 = validateConfig(actionType, allowedHosts2, { + const validatedConfig2 = validateConfig(connectorType, allowedHosts2, { configurationUtilities: configUtils, }); expect(validatedConfig2.host).toEqual(allowedHosts2.host); @@ -283,7 +282,7 @@ describe('config validation', () => { expect(validatedConfig2.from).toEqual(allowedHosts2.from); expect(() => { - validateConfig(actionType, notAllowedHosts1, { + validateConfig(connectorType, notAllowedHosts1, { configurationUtilities: configUtils, }); }).toThrowErrorMatchingInlineSnapshot( @@ -291,7 +290,7 @@ describe('config validation', () => { ); expect(() => { - validateConfig(actionType, notAllowedHosts2, { configurationUtilities: configUtils }); + validateConfig(connectorType, notAllowedHosts2, { configurationUtilities: configUtils }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type config: [host] value 'smtp.gmail.com' is not in the allowedHosts configuration"` ); @@ -301,14 +300,9 @@ describe('config validation', () => { const configUtils = actionsConfigMock.create(); configUtils.validateEmailAddresses.mockImplementation(validateEmailAddressesImpl); - const basicActionType = getActionType({ - logger: mockedLogger, - configurationUtilities: configUtils, - }); - expect(() => { validateConfig( - basicActionType, + connectorType, { from: 'badmail', service: 'gmail', @@ -328,7 +322,7 @@ describe('secrets validation', () => { user: 'bob', password: 'supersecret', }; - expect(validateSecrets(actionType, secrets, { configurationUtilities })).toEqual({ + expect(validateSecrets(connectorType, secrets, { configurationUtilities })).toEqual({ ...secrets, clientSecret: null, }); @@ -340,11 +334,11 @@ describe('secrets validation', () => { password: null, clientSecret: null, }; - expect(validateSecrets(actionType, {}, { configurationUtilities })).toEqual(secrets); - expect(validateSecrets(actionType, { user: null }, { configurationUtilities })).toEqual( + expect(validateSecrets(connectorType, {}, { configurationUtilities })).toEqual(secrets); + expect(validateSecrets(connectorType, { user: null }, { configurationUtilities })).toEqual( secrets ); - expect(validateSecrets(actionType, { password: null }, { configurationUtilities })).toEqual( + expect(validateSecrets(connectorType, { password: null }, { configurationUtilities })).toEqual( secrets ); }); @@ -353,7 +347,7 @@ describe('secrets validation', () => { const secrets: Record = { clientSecret: '12345678', }; - expect(validateSecrets(actionType, secrets, { configurationUtilities })).toEqual({ + expect(validateSecrets(connectorType, secrets, { configurationUtilities })).toEqual({ ...secrets, user: null, password: null, @@ -370,7 +364,7 @@ describe('connector validation: secrets with config', () => { const config: Record = { hasAuth: true, }; - expect(validateConnector(actionType, { config, secrets })).toBeNull(); + expect(validateConnector(connectorType, { config, secrets })).toBeNull(); }); test('connector validation succeeds when username/password not filled for hasAuth false', () => { @@ -382,10 +376,10 @@ describe('connector validation: secrets with config', () => { const config: Record = { hasAuth: false, }; - expect(validateConnector(actionType, { config, secrets })).toBeNull(); - expect(validateConnector(actionType, { config, secrets: {} })).toBeNull(); - expect(validateConnector(actionType, { config, secrets: { user: null } })).toBeNull(); - expect(validateConnector(actionType, { config, secrets: { password: null } })).toBeNull(); + expect(validateConnector(connectorType, { config, secrets })).toBeNull(); + expect(validateConnector(connectorType, { config, secrets: {} })).toBeNull(); + expect(validateConnector(connectorType, { config, secrets: { user: null } })).toBeNull(); + expect(validateConnector(connectorType, { config, secrets: { password: null } })).toBeNull(); }); test('connector validation fails when username/password was populated for hasAuth true', () => { @@ -398,7 +392,7 @@ describe('connector validation: secrets with config', () => { }; // invalid user expect(() => { - validateConnector(actionType, { config, secrets }); + validateConnector(connectorType, { config, secrets }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type connector: [user] is required"` ); @@ -411,7 +405,7 @@ describe('connector validation: secrets with config', () => { const config: Record = { service: 'exchange_server', }; - expect(validateConnector(actionType, { config, secrets })).toBeNull(); + expect(validateConnector(connectorType, { config, secrets })).toBeNull(); }); test('connector validation fails when service is exchange_server and clientSecret is not populated', () => { @@ -423,7 +417,7 @@ describe('connector validation: secrets with config', () => { }; // invalid user expect(() => { - validateConnector(actionType, { config, secrets }); + validateConnector(connectorType, { config, secrets }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type connector: [clientSecret] is required"` ); @@ -437,7 +431,8 @@ describe('params validation', () => { subject: 'this is a test', message: 'this is the message', }; - expect(validateParams(actionType, params, { configurationUtilities })).toMatchInlineSnapshot(` + expect(validateParams(connectorType, params, { configurationUtilities })) + .toMatchInlineSnapshot(` Object { "bcc": Array [], "cc": Array [], @@ -457,7 +452,7 @@ describe('params validation', () => { test('params validation fails when params is not valid', () => { // empty object expect(() => { - validateParams(actionType, {}, { configurationUtilities }); + validateParams(connectorType, {}, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action params: [subject]: expected value of type [string] but got [undefined]"` ); @@ -467,14 +462,9 @@ describe('params validation', () => { const configUtils = actionsConfigMock.create(); configUtils.validateEmailAddresses.mockImplementation(validateEmailAddressesImpl); - const basicActionType = getActionType({ - logger: mockedLogger, - configurationUtilities: configUtils, - }); - expect(() => { validateParams( - basicActionType, + connectorType, { to: ['to@example.com'], cc: ['cc@example.com'], @@ -496,7 +486,7 @@ describe('params validation', () => { }); describe('execute()', () => { - const config: ActionTypeConfigType = { + const config: ConnectorTypeConfigType = { service: '__json', host: 'a host', port: 42, @@ -507,7 +497,7 @@ describe('execute()', () => { tenantId: null, oauthTokenUrl: null, }; - const secrets: ActionTypeSecretsType = { + const secrets: ConnectorTypeSecretsType = { user: 'bob', password: 'supersecret', clientSecret: null, @@ -525,17 +515,18 @@ describe('execute()', () => { }; const actionId = 'some-id'; - const executorOptions: EmailActionTypeExecutorOptions = { + const executorOptions: EmailConnectorTypeExecutorOptions = { actionId, config, params, secrets, services, + configurationUtilities: actionsConfigMock.create(), }; test('ensure parameters are as expected', async () => { sendEmailMock.mockReset(); - const result = await actionType.executor(executorOptions); + const result = await connectorType.executor(executorOptions); expect(result).toMatchInlineSnapshot(` Object { "actionId": "some-id", @@ -578,7 +569,7 @@ describe('execute()', () => { }); test('parameters are as expected with no auth', async () => { - const customExecutorOptions: EmailActionTypeExecutorOptions = { + const customExecutorOptions: EmailConnectorTypeExecutorOptions = { ...executorOptions, config: { ...config, @@ -593,7 +584,7 @@ describe('execute()', () => { }; sendEmailMock.mockReset(); - await actionType.executor(customExecutorOptions); + await connectorType.executor(customExecutorOptions); delete sendEmailMock.mock.calls[0][1].configurationUtilities; expect(sendEmailMock.mock.calls[0][1]).toMatchInlineSnapshot(` Object { @@ -629,7 +620,7 @@ describe('execute()', () => { }); test('parameters are as expected when using elastic_cloud service', async () => { - const customExecutorOptions: EmailActionTypeExecutorOptions = { + const customExecutorOptions: EmailConnectorTypeExecutorOptions = { ...executorOptions, config: { ...config, @@ -644,7 +635,7 @@ describe('execute()', () => { }; sendEmailMock.mockReset(); - await actionType.executor(customExecutorOptions); + await connectorType.executor(customExecutorOptions); delete sendEmailMock.mock.calls[0][1].configurationUtilities; expect(sendEmailMock.mock.calls[0][1]).toMatchInlineSnapshot(` Object { @@ -680,7 +671,7 @@ describe('execute()', () => { }); test('returns expected result when an error is thrown', async () => { - const customExecutorOptions: EmailActionTypeExecutorOptions = { + const customExecutorOptions: EmailConnectorTypeExecutorOptions = { ...executorOptions, config: { ...config, @@ -696,7 +687,7 @@ describe('execute()', () => { sendEmailMock.mockReset(); sendEmailMock.mockRejectedValue(new Error('wops')); - const result = await actionType.executor(customExecutorOptions); + const result = await connectorType.executor(customExecutorOptions); expect(result).toMatchInlineSnapshot(` Object { "actionId": "some-id", @@ -708,7 +699,7 @@ describe('execute()', () => { }); test('renders parameter templates as expected', async () => { - expect(actionType.renderParameterTemplates).toBeTruthy(); + expect(connectorType.renderParameterTemplates).toBeTruthy(); const paramsWithTemplates = { to: [], cc: ['{{rogue}}'], @@ -723,7 +714,7 @@ describe('execute()', () => { const variables = { rogue: '*bold*', }; - const renderedParams = actionType.renderParameterTemplates!(paramsWithTemplates, variables); + const renderedParams = connectorType.renderParameterTemplates!(paramsWithTemplates, variables); // Yes, this is tested in the snapshot below, but it's double-escaped there, // so easier to see here that the escaping is correct. expect(renderedParams.message).toBe('\\*bold\\*'); @@ -749,13 +740,12 @@ describe('execute()', () => { }); test('provides a footer link to Kibana when publicBaseUrl is defined', async () => { - const actionTypeWithPublicUrl = getActionType({ + const connectorTypeWithPublicUrl = getConnectorType({ logger: mockedLogger, - configurationUtilities: actionsConfigMock.create(), publicBaseUrl: 'https://localhost:1234/foo/bar', }); - await actionTypeWithPublicUrl.executor(executorOptions); + await connectorTypeWithPublicUrl.executor(executorOptions); expect(sendEmailMock).toHaveBeenCalledTimes(1); const sendMailCall = sendEmailMock.mock.calls[0][1]; @@ -769,13 +759,12 @@ describe('execute()', () => { }); test('allows to generate a deep link into Kibana when publicBaseUrl is defined', async () => { - const actionTypeWithPublicUrl = getActionType({ + const connectorTypeWithPublicUrl = getConnectorType({ logger: mockedLogger, - configurationUtilities: actionsConfigMock.create(), publicBaseUrl: 'https://localhost:1234/foo/bar', }); - const customExecutorOptions: EmailActionTypeExecutorOptions = { + const customExecutorOptions: EmailConnectorTypeExecutorOptions = { ...executorOptions, params: { ...params, @@ -786,7 +775,7 @@ describe('execute()', () => { }, }; - await actionTypeWithPublicUrl.executor(customExecutorOptions); + await connectorTypeWithPublicUrl.executor(customExecutorOptions); expect(sendEmailMock).toHaveBeenCalledTimes(1); const sendMailCall = sendEmailMock.mock.calls[0][1]; @@ -803,19 +792,15 @@ describe('execute()', () => { const configUtils = actionsConfigMock.create(); configUtils.validateEmailAddresses.mockImplementation(validateEmailAddressesImpl); - const testActionType = getActionType({ - logger: mockedLogger, - configurationUtilities: configUtils, - }); - - const customExecutorOptions: EmailActionTypeExecutorOptions = { + const customExecutorOptions: EmailConnectorTypeExecutorOptions = { ...executorOptions, params: { ...params, }, + configurationUtilities: configUtils, }; - const result = await testActionType.executor(customExecutorOptions); + const result = await connectorType.executor(customExecutorOptions); expect(result).toMatchInlineSnapshot(` Object { "actionId": "some-id", diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/email/index.ts similarity index 84% rename from x-pack/plugins/actions/server/builtin_action_types/email.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/email/index.ts index 8d70f1de208b6..e146baba68fbc 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/email/index.ts @@ -11,39 +11,40 @@ import { schema, TypeOf } from '@kbn/config-schema'; import nodemailerGetService from 'nodemailer/lib/well-known'; import SMTPConnection from 'nodemailer/lib/smtp-connection'; import { Logger } from '@kbn/core/server'; +import type { + ActionType as ConnectorType, + ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, + ActionTypeExecutorResult as ConnectorTypeExecutorResult, + ValidatorServices, +} from '@kbn/actions-plugin/server/types'; import { AlertingConnectorFeatureId, - AdditionalEmailServices, - withoutMustacheTemplate, UptimeConnectorFeatureId, SecurityConnectorFeatureId, -} from '../../common'; - -import { sendEmail, JSON_TRANSPORT_SERVICE, SendEmailOptions, Transport } from './lib/send_email'; -import { portSchema } from './lib/schemas'; +} from '@kbn/actions-plugin/common/connector_feature_config'; +import { withoutMustacheTemplate } from '@kbn/actions-plugin/common'; import { - ActionType, - ActionTypeExecutorOptions, - ActionTypeExecutorResult, - ValidatorServices, -} from '../types'; -import { ActionsConfigurationUtilities } from '../actions_config'; -import { renderMustacheString, renderMustacheObject } from '../lib/mustache_renderer'; - -export type EmailActionType = ActionType< - ActionTypeConfigType, - ActionTypeSecretsType, + renderMustacheObject, + renderMustacheString, +} from '@kbn/actions-plugin/server/lib/mustache_renderer'; +import { AdditionalEmailServices } from '../../../../common'; +import { sendEmail, JSON_TRANSPORT_SERVICE, SendEmailOptions, Transport } from './send_email'; +import { portSchema } from '../../lib/schemas'; + +export type EmailConnectorType = ConnectorType< + ConnectorTypeConfigType, + ConnectorTypeSecretsType, ActionParamsType, unknown >; -export type EmailActionTypeExecutorOptions = ActionTypeExecutorOptions< - ActionTypeConfigType, - ActionTypeSecretsType, +export type EmailConnectorTypeExecutorOptions = ConnectorTypeExecutorOptions< + ConnectorTypeConfigType, + ConnectorTypeSecretsType, ActionParamsType >; // config definition -export type ActionTypeConfigType = TypeOf; +export type ConnectorTypeConfigType = TypeOf; // these values for `service` require users to fill in host/port/secure export const CUSTOM_HOST_PORT_SERVICES: string[] = [AdditionalEmailServices.OTHER]; @@ -70,7 +71,10 @@ const ConfigSchemaProps = { const ConfigSchema = schema.object(ConfigSchemaProps); -function validateConfig(configObject: ActionTypeConfigType, validatorServices: ValidatorServices) { +function validateConfig( + configObject: ConnectorTypeConfigType, + validatorServices: ValidatorServices +) { const config = configObject; const { configurationUtilities } = validatorServices; @@ -131,7 +135,7 @@ function validateConfig(configObject: ActionTypeConfigType, validatorServices: V // secrets definition -export type ActionTypeSecretsType = TypeOf; +export type ConnectorTypeSecretsType = TypeOf; const SecretsSchemaProps = { user: schema.nullable(schema.string()), @@ -156,7 +160,7 @@ const ParamsSchemaProps = { kibanaFooterLink: schema.object({ path: schema.string({ defaultValue: '/' }), text: schema.string({ - defaultValue: i18n.translate('xpack.actions.builtin.email.kibanaFooterLinkText', { + defaultValue: i18n.translate('xpack.stackConnectors.email.kibanaFooterLinkText', { defaultMessage: 'Go to Kibana', }), }), @@ -187,15 +191,14 @@ function validateParams(paramsObject: unknown, validatorServices: ValidatorServi } } -interface GetActionTypeParams { +interface GetConnectorTypeParams { logger: Logger; publicBaseUrl?: string; - configurationUtilities: ActionsConfigurationUtilities; } function validateConnector( - config: ActionTypeConfigType, - secrets: ActionTypeSecretsType + config: ConnectorTypeConfigType, + secrets: ConnectorTypeSecretsType ): string | null { if (config.service === AdditionalEmailServices.EXCHANGE) { if (secrets.clientSecret == null) { @@ -212,14 +215,14 @@ function validateConnector( return null; } -// action type definition -export const ActionTypeId = '.email'; -export function getActionType(params: GetActionTypeParams): EmailActionType { - const { logger, publicBaseUrl, configurationUtilities } = params; +// connector type definition +export const ConnectorTypeId = '.email'; +export function getConnectorType(params: GetConnectorTypeParams): EmailConnectorType { + const { logger, publicBaseUrl } = params; return { - id: ActionTypeId, + id: ConnectorTypeId, minimumLicenseRequired: 'gold', - name: i18n.translate('xpack.actions.builtin.emailTitle', { + name: i18n.translate('xpack.stackConnectors.email.title', { defaultMessage: 'Email', }), supportedFeatureIds: [ @@ -242,7 +245,7 @@ export function getActionType(params: GetActionTypeParams): EmailActionType { connector: validateConnector, }, renderParameterTemplates, - executor: curry(executor)({ logger, publicBaseUrl, configurationUtilities }), + executor: curry(executor)({ logger, publicBaseUrl }), }; } @@ -264,18 +267,17 @@ async function executor( { logger, publicBaseUrl, - configurationUtilities, }: { - logger: GetActionTypeParams['logger']; - publicBaseUrl: GetActionTypeParams['publicBaseUrl']; - configurationUtilities: ActionsConfigurationUtilities; + logger: GetConnectorTypeParams['logger']; + publicBaseUrl: GetConnectorTypeParams['publicBaseUrl']; }, - execOptions: EmailActionTypeExecutorOptions -): Promise> { + execOptions: EmailConnectorTypeExecutorOptions +): Promise> { const actionId = execOptions.actionId; const config = execOptions.config; const secrets = execOptions.secrets; const params = execOptions.params; + const configurationUtilities = execOptions.configurationUtilities; const connectorTokenClient = execOptions.services.connectorTokenClient; const emails = params.to.concat(params.cc).concat(params.bcc); @@ -351,7 +353,7 @@ async function executor( try { result = await sendEmail(logger, sendEmailOptions, connectorTokenClient); } catch (err) { - const message = i18n.translate('xpack.actions.builtin.email.errorSendingErrorMessage', { + const message = i18n.translate('xpack.stackConnectors.email.errorSendingErrorMessage', { defaultMessage: 'error sending email', }); return { @@ -396,16 +398,16 @@ function getFooterMessage({ publicBaseUrl, kibanaFooterLink, }: { - publicBaseUrl: GetActionTypeParams['publicBaseUrl']; + publicBaseUrl: GetConnectorTypeParams['publicBaseUrl']; kibanaFooterLink: ActionParamsType['kibanaFooterLink']; }) { if (!publicBaseUrl) { - return i18n.translate('xpack.actions.builtin.email.sentByKibanaMessage', { + return i18n.translate('xpack.stackConnectors.email.sentByKibanaMessage', { defaultMessage: 'This message was sent by Kibana.', }); } - return i18n.translate('xpack.actions.builtin.email.customViewInKibanaMessage', { + return i18n.translate('xpack.stackConnectors.email.customViewInKibanaMessage', { defaultMessage: 'This message was sent by Kibana. [{kibanaFooterLinkText}]({link}).', values: { kibanaFooterLinkText: kibanaFooterLink.text, diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/email/send_email.test.ts similarity index 97% rename from x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/email/send_email.test.ts index fe6fc3492492a..9aa780788bc10 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/email/send_email.test.ts @@ -10,12 +10,12 @@ import { Logger } from '@kbn/core/server'; import { sendEmail } from './send_email'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import nodemailer from 'nodemailer'; -import { ProxySettings } from '../../types'; -import { actionsConfigMock } from '../../actions_config.mock'; -import { CustomHostSettings } from '../../config'; +import { ProxySettings } from '@kbn/actions-plugin/server/types'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { CustomHostSettings } from '@kbn/actions-plugin/server/config'; import { sendEmailGraphApi } from './send_email_graph_api'; -import { getOAuthClientCredentialsAccessToken } from './get_oauth_client_credentials_access_token'; -import { connectorTokenClientMock } from './connector_token_client.mock'; +import { getOAuthClientCredentialsAccessToken } from '@kbn/actions-plugin/server/lib/get_oauth_client_credentials_access_token'; +import { connectorTokenClientMock } from '@kbn/actions-plugin/server/lib/connector_token_client.mock'; jest.mock('nodemailer', () => ({ createTransport: jest.fn(), @@ -23,7 +23,7 @@ jest.mock('nodemailer', () => ({ jest.mock('./send_email_graph_api', () => ({ sendEmailGraphApi: jest.fn(), })); -jest.mock('./get_oauth_client_credentials_access_token', () => ({ +jest.mock('@kbn/actions-plugin/server/lib/get_oauth_client_credentials_access_token', () => ({ getOAuthClientCredentialsAccessToken: jest.fn(), })); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/email/send_email.ts similarity index 94% rename from x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/email/send_email.ts index 2fee4dd8b377d..b829def7617c3 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/email/send_email.ts @@ -11,13 +11,16 @@ import nodemailer from 'nodemailer'; import { default as MarkdownIt } from 'markdown-it'; import { Logger } from '@kbn/core/server'; -import { ActionsConfigurationUtilities } from '../../actions_config'; -import { CustomHostSettings } from '../../config'; -import { getNodeSSLOptions, getSSLSettingsFromConfig } from './get_node_ssl_options'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { CustomHostSettings } from '@kbn/actions-plugin/server/config'; +import { + getNodeSSLOptions, + getSSLSettingsFromConfig, +} from '@kbn/actions-plugin/server/lib/get_node_ssl_options'; +import { ConnectorTokenClientContract, ProxySettings } from '@kbn/actions-plugin/server/types'; +import { getOAuthClientCredentialsAccessToken } from '@kbn/actions-plugin/server/lib/get_oauth_client_credentials_access_token'; +import { AdditionalEmailServices } from '../../../../common'; import { sendEmailGraphApi } from './send_email_graph_api'; -import { ConnectorTokenClientContract, ProxySettings } from '../../types'; -import { AdditionalEmailServices } from '../../../common'; -import { getOAuthClientCredentialsAccessToken } from './get_oauth_client_credentials_access_token'; // an email "service" which doesn't actually send, just returns what it would send export const JSON_TRANSPORT_SERVICE = '__json'; diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email_graph_api.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/email/send_email_graph_api.test.ts similarity index 97% rename from x-pack/plugins/actions/server/builtin_action_types/lib/send_email_graph_api.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/email/send_email_graph_api.test.ts index fc67302b9bb0a..4ab03837f416b 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email_graph_api.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/email/send_email_graph_api.test.ts @@ -11,9 +11,9 @@ jest.mock('axios', () => ({ import axios from 'axios'; import { Logger } from '@kbn/core/server'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { actionsConfigMock } from '../../actions_config.mock'; -import { CustomHostSettings } from '../../config'; -import { ProxySettings } from '../../types'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { CustomHostSettings } from '@kbn/actions-plugin/server/config'; +import { ProxySettings } from '@kbn/actions-plugin/server/types'; import { sendEmailGraphApi } from './send_email_graph_api'; const createAxiosInstanceMock = axios.create as jest.Mock; diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email_graph_api.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/email/send_email_graph_api.ts similarity index 93% rename from x-pack/plugins/actions/server/builtin_action_types/lib/send_email_graph_api.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/email/send_email_graph_api.ts index dad4154d5bac6..40177e50a0d18 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email_graph_api.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/email/send_email_graph_api.ts @@ -9,8 +9,8 @@ import stringify from 'json-stringify-safe'; import axios, { AxiosInstance, AxiosResponse } from 'axios'; import { Logger } from '@kbn/core/server'; -import { request } from '../../lib/axios_utils'; -import { ActionsConfigurationUtilities } from '../../actions_config'; +import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; import { SendEmailOptions } from './send_email'; interface SendEmailGraphApiOptions { diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/es_index/index.test.ts similarity index 83% rename from x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/es_index/index.test.ts index 8a87255dc1210..9968a67e87b45 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/es_index/index.test.ts @@ -5,44 +5,41 @@ * 2.0. */ -jest.mock('./lib/send_email', () => ({ - sendEmail: jest.fn(), -})); import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { validateConfig, validateParams } from '../lib'; -import { createActionTypeRegistry } from './index.test'; -import { actionsMock } from '../mocks'; +import { validateConfig, validateParams } from '@kbn/actions-plugin/server/lib'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; import { ActionParamsType, - ActionTypeConfigType, - ESIndexActionType, - ESIndexActionTypeExecutorOptions, -} from './es_index'; -import { AlertHistoryEsIndexConnectorId } from '../../common'; + ConnectorTypeConfigType, + ESIndexConnectorType, + ESIndexConnectorTypeExecutorOptions, + getConnectorType, +} from '.'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { ActionsConfigurationUtilities } from '../actions_config'; - -const ACTION_TYPE_ID = '.index'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { loggerMock } from '@kbn/logging-mocks'; +import { Logger } from '@kbn/logging'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { AlertHistoryEsIndexConnectorId } from '@kbn/actions-plugin/common'; const services = actionsMock.createServices(); +const mockedLogger: jest.Mocked = loggerMock.create(); -let actionType: ESIndexActionType; +let connectorType: ESIndexConnectorType; let configurationUtilities: ActionsConfigurationUtilities; -beforeAll(() => { - const { actionTypeRegistry } = createActionTypeRegistry(); - actionType = actionTypeRegistry.get(ACTION_TYPE_ID); - configurationUtilities = actionTypeRegistry.getUtils(); -}); - beforeEach(() => { jest.resetAllMocks(); + configurationUtilities = actionsConfigMock.create(); + connectorType = getConnectorType({ + logger: mockedLogger, + }); }); -describe('actionTypeRegistry.get() works', () => { - test('action type static data is as expected', () => { - expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('Index'); +describe('connector registration', () => { + test('returns connector type', () => { + expect(connectorType.id).toEqual('.index'); + expect(connectorType.name).toEqual('Index'); }); }); @@ -53,7 +50,7 @@ describe('config validation', () => { refresh: false, }; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ ...config, index: 'testing-123', refresh: false, @@ -61,7 +58,7 @@ describe('config validation', () => { }); config.executionTimeField = 'field-123'; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ ...config, index: 'testing-123', refresh: false, @@ -69,7 +66,7 @@ describe('config validation', () => { }); config.executionTimeField = null; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ ...config, index: 'testing-123', refresh: false, @@ -79,7 +76,7 @@ describe('config validation', () => { delete config.index; expect(() => { - validateConfig(actionType, { index: 666 }, { configurationUtilities }); + validateConfig(connectorType, { index: 666 }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type config: [index]: expected value of type [string] but got [number]"` ); @@ -87,7 +84,7 @@ describe('config validation', () => { expect(() => { validateConfig( - actionType, + connectorType, { index: 'testing-123', executionTimeField: true }, { configurationUtilities } ); @@ -100,7 +97,7 @@ describe('config validation', () => { delete config.refresh; expect(() => { validateConfig( - actionType, + connectorType, { index: 'testing-123', refresh: 'foo' }, { configurationUtilities } ); @@ -115,7 +112,7 @@ describe('config validation', () => { }; expect(() => { - validateConfig(actionType, baseConfig, { configurationUtilities }); + validateConfig(connectorType, baseConfig, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type config: [index]: expected value of type [string] but got [undefined]"` ); @@ -128,7 +125,8 @@ describe('params validation', () => { documents: [{ rando: 'thing' }], indexOverride: null, }; - expect(validateParams(actionType, params, { configurationUtilities })).toMatchInlineSnapshot(` + expect(validateParams(connectorType, params, { configurationUtilities })) + .toMatchInlineSnapshot(` Object { "documents": Array [ Object { @@ -142,20 +140,20 @@ describe('params validation', () => { test('params validation fails when params is not valid', () => { expect(() => { - validateParams(actionType, { documents: [{}], jim: 'bob' }, { configurationUtilities }); + validateParams(connectorType, { documents: [{}], jim: 'bob' }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action params: [jim]: definition for this key is missing"` ); expect(() => { - validateParams(actionType, {}, { configurationUtilities }); + validateParams(connectorType, {}, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action params: [documents]: expected value of type [array] but got [undefined]"` ); expect(() => { validateParams( - actionType, + connectorType, { documents: ['should be an object'] }, { configurationUtilities } ); @@ -168,9 +166,9 @@ describe('params validation', () => { describe('execute()', () => { test('ensure parameters are as expected', async () => { const secrets = {}; - let config: ActionTypeConfigType; + let config: ConnectorTypeConfigType; let params: ActionParamsType; - let executorOptions: ESIndexActionTypeExecutorOptions; + let executorOptions: ESIndexConnectorTypeExecutorOptions; // minimal params config = { index: 'index-value', refresh: false, executionTimeField: null }; @@ -187,11 +185,12 @@ describe('execute()', () => { secrets, params, services, + configurationUtilities, }; const scopedClusterClient = elasticsearchClientMock .createClusterClient() .asScoped().asCurrentUser; - await actionType.executor({ + await connectorType.executor({ ...executorOptions, services: { ...services, scopedClusterClient }, }); @@ -224,9 +223,9 @@ describe('execute()', () => { indexOverride: null, }; - executorOptions = { actionId, config, secrets, params, services }; + executorOptions = { actionId, config, secrets, params, services, configurationUtilities }; scopedClusterClient.bulk.mockClear(); - await actionType.executor({ + await connectorType.executor({ ...executorOptions, services: { ...services, scopedClusterClient }, }); @@ -266,10 +265,10 @@ describe('execute()', () => { indexOverride: null, }; - executorOptions = { actionId, config, secrets, params, services }; + executorOptions = { actionId, config, secrets, params, services, configurationUtilities }; scopedClusterClient.bulk.mockClear(); - await actionType.executor({ + await connectorType.executor({ ...executorOptions, services: { ...services, scopedClusterClient }, }); @@ -302,9 +301,9 @@ describe('execute()', () => { indexOverride: null, }; - executorOptions = { actionId, config, secrets, params, services }; + executorOptions = { actionId, config, secrets, params, services, configurationUtilities }; scopedClusterClient.bulk.mockClear(); - await actionType.executor({ + await connectorType.executor({ ...executorOptions, services: { ...services, scopedClusterClient }, }); @@ -340,7 +339,7 @@ describe('execute()', () => { }); test('renders parameter templates as expected', async () => { - expect(actionType.renderParameterTemplates).toBeTruthy(); + expect(connectorType.renderParameterTemplates).toBeTruthy(); const paramsWithTemplates = { documents: [{ hello: '{{who}}' }], indexOverride: null, @@ -348,7 +347,7 @@ describe('execute()', () => { const variables = { who: 'world', }; - const renderedParams = actionType.renderParameterTemplates!( + const renderedParams = connectorType.renderParameterTemplates!( paramsWithTemplates, variables, 'action-type-id' @@ -366,7 +365,7 @@ describe('execute()', () => { }); test('ignores indexOverride for generic es index connector', async () => { - expect(actionType.renderParameterTemplates).toBeTruthy(); + expect(connectorType.renderParameterTemplates).toBeTruthy(); const paramsWithTemplates = { documents: [{ hello: '{{who}}' }], indexOverride: 'hello-world', @@ -374,7 +373,7 @@ describe('execute()', () => { const variables = { who: 'world', }; - const renderedParams = actionType.renderParameterTemplates!( + const renderedParams = connectorType.renderParameterTemplates!( paramsWithTemplates, variables, 'action-type-id' @@ -392,7 +391,7 @@ describe('execute()', () => { }); test('renders parameter templates as expected for preconfigured alert history connector', async () => { - expect(actionType.renderParameterTemplates).toBeTruthy(); + expect(connectorType.renderParameterTemplates).toBeTruthy(); const paramsWithTemplates = { documents: [{ hello: '{{who}}' }], indexOverride: null, @@ -423,7 +422,7 @@ describe('execute()', () => { alertStateAnotherValue: 'yes', }, }; - const renderedParams = actionType.renderParameterTemplates!( + const renderedParams = connectorType.renderParameterTemplates!( paramsWithTemplates, variables, AlertHistoryEsIndexConnectorId @@ -472,7 +471,7 @@ describe('execute()', () => { }); test('passes through indexOverride for preconfigured alert history connector', async () => { - expect(actionType.renderParameterTemplates).toBeTruthy(); + expect(connectorType.renderParameterTemplates).toBeTruthy(); const paramsWithTemplates = { documents: [{ hello: '{{who}}' }], indexOverride: 'hello-world', @@ -503,7 +502,7 @@ describe('execute()', () => { alertStateAnotherValue: 'yes', }, }; - const renderedParams = actionType.renderParameterTemplates!( + const renderedParams = connectorType.renderParameterTemplates!( paramsWithTemplates, variables, AlertHistoryEsIndexConnectorId @@ -552,7 +551,7 @@ describe('execute()', () => { }); test('throws error for preconfigured alert history index when no variables are available', async () => { - expect(actionType.renderParameterTemplates).toBeTruthy(); + expect(connectorType.renderParameterTemplates).toBeTruthy(); const paramsWithTemplates = { documents: [{ hello: '{{who}}' }], indexOverride: null, @@ -560,7 +559,7 @@ describe('execute()', () => { const variables = {}; expect(() => - actionType.renderParameterTemplates!( + connectorType.renderParameterTemplates!( paramsWithTemplates, variables, AlertHistoryEsIndexConnectorId @@ -605,8 +604,16 @@ describe('execute()', () => { ], }); - expect(await actionType.executor({ actionId, config, secrets, params, services })) - .toMatchInlineSnapshot(` + expect( + await connectorType.executor({ + actionId, + config, + secrets, + params, + services, + configurationUtilities, + }) + ).toMatchInlineSnapshot(` Object { "actionId": "some-id", "message": "error indexing documents", diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/es_index/index.ts similarity index 77% rename from x-pack/plugins/actions/server/builtin_action_types/es_index.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/es_index/index.ts index cc5b58f211671..6346e166f97ac 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/es_index/index.ts @@ -9,27 +9,38 @@ import { curry, find } from 'lodash'; import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; -import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; -import { renderMustacheObject } from '../lib/mustache_renderer'; +import type { + ActionType as ConnectorType, + ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, + ActionTypeExecutorResult as ConnectorTypeExecutorResult, +} from '@kbn/actions-plugin/server/types'; +import { renderMustacheObject } from '@kbn/actions-plugin/server/lib/mustache_renderer'; import { - buildAlertHistoryDocument, - AlertHistoryEsIndexConnectorId, AlertingConnectorFeatureId, UptimeConnectorFeatureId, SecurityConnectorFeatureId, -} from '../../common'; -import { ALERT_HISTORY_PREFIX } from '../../common/alert_history_schema'; +} from '@kbn/actions-plugin/common/types'; +import { + AlertHistoryEsIndexConnectorId, + ALERT_HISTORY_PREFIX, + buildAlertHistoryDocument, +} from '@kbn/actions-plugin/common'; -export type ESIndexActionType = ActionType; -export type ESIndexActionTypeExecutorOptions = ActionTypeExecutorOptions< - ActionTypeConfigType, +export type ESIndexConnectorType = ConnectorType< + ConnectorTypeConfigType, + {}, + ActionParamsType, + unknown +>; +export type ESIndexConnectorTypeExecutorOptions = ConnectorTypeExecutorOptions< + ConnectorTypeConfigType, {}, ActionParamsType >; // config definition -export type ActionTypeConfigType = TypeOf; +export type ConnectorTypeConfigType = TypeOf; const ConfigSchema = schema.object({ index: schema.string(), @@ -57,13 +68,13 @@ const ParamsSchema = schema.object({ ), }); -export const ActionTypeId = '.index'; -// action type definition -export function getActionType({ logger }: { logger: Logger }): ESIndexActionType { +export const ConnectorTypeId = '.index'; +// connector type definition +export function getConnectorType({ logger }: { logger: Logger }): ESIndexConnectorType { return { - id: ActionTypeId, + id: ConnectorTypeId, minimumLicenseRequired: 'basic', - name: i18n.translate('xpack.actions.builtin.esIndexTitle', { + name: i18n.translate('xpack.stackConnectors.esIndex.title', { defaultMessage: 'Index', }), supportedFeatureIds: [ @@ -88,8 +99,8 @@ export function getActionType({ logger }: { logger: Logger }): ESIndexActionType async function executor( { logger }: { logger: Logger }, - execOptions: ESIndexActionTypeExecutorOptions -): Promise> { + execOptions: ESIndexConnectorTypeExecutorOptions +): Promise> { const actionId = execOptions.actionId; const config = execOptions.config; const params = execOptions.params; @@ -156,8 +167,8 @@ function wrapErr( errMessage: string, actionId: string, logger: Logger -): ActionTypeExecutorResult { - const message = i18n.translate('xpack.actions.builtin.esIndex.errorIndexingErrorMessage', { +): ConnectorTypeExecutorResult { + const message = i18n.translate('xpack.stackConnectors.esIndex.errorIndexingErrorMessage', { defaultMessage: 'error indexing documents', }); logger.error(`error indexing documents: ${errMessage}`); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/stack/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/index.ts new file mode 100644 index 0000000000000..18fc54872261e --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/index.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { + getConnectorType as getEmailConnectorType, + ConnectorTypeId as EmailConnectorTypeId, +} from './email'; +export type { ActionParamsType as EmailActionParams } from './email'; + +export { + getConnectorType as getIndexConnectorType, + ConnectorTypeId as IndexConnectorTypeId, +} from './es_index'; +export type { ActionParamsType as IndexActionParams } from './es_index'; + +export { + getConnectorType as getPagerDutyConnectorType, + ConnectorTypeId as PagerDutyConnectorTypeId, +} from './pagerduty'; +export type { ActionParamsType as PagerDutyActionParams } from './pagerduty'; + +export { + getConnectorType as getServerLogConnectorType, + ConnectorTypeId as ServerLogConnectorTypeId, +} from './server_log'; +export type { ActionParamsType as ServerLogActionParams } from './server_log'; + +export { + getConnectorType as getSlackConnectorType, + ConnectorTypeId as SlackConnectorTypeId, +} from './slack'; +export type { ActionParamsType as SlackActionParams } from './slack'; + +export { + getConnectorType as getTeamsConnectorType, + ConnectorTypeId as TeamsConnectorTypeId, +} from './teams'; +export type { ActionParamsType as TeamsActionParams } from './teams'; + +export { + getConnectorType as getWebhookConnectorType, + ConnectorTypeId as WebhookConnectorTypeId, +} from './webhook'; +export type { ActionParamsType as WebhookActionParams } from './webhook'; diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/pagerduty/index.test.ts similarity index 83% rename from x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/pagerduty/index.test.ts index a601532366fc4..eaf073f732f77 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/pagerduty/index.test.ts @@ -7,61 +7,51 @@ import moment from 'moment'; -jest.mock('./lib/post_pagerduty', () => ({ +jest.mock('./post_pagerduty', () => ({ postPagerduty: jest.fn(), })); - -import { Services } from '../types'; -import { validateConfig, validateSecrets, validateParams } from '../lib'; -import { postPagerduty } from './lib/post_pagerduty'; -import { createActionTypeRegistry } from './index.test'; +import { Services } from '@kbn/actions-plugin/server/types'; +import { validateConfig, validateSecrets, validateParams } from '@kbn/actions-plugin/server/lib'; +import { postPagerduty } from './post_pagerduty'; import { Logger } from '@kbn/core/server'; -import { actionsConfigMock } from '../actions_config.mock'; -import { actionsMock } from '../mocks'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; import { ActionParamsType, - ActionTypeConfigType, - ActionTypeSecretsType, - getActionType, - PagerDutyActionType, - PagerDutyActionTypeExecutorOptions, -} from './pagerduty'; -import { ActionsConfigurationUtilities } from '../actions_config'; + getConnectorType, + PagerDutyConnectorType, + PagerDutyConnectorTypeExecutorOptions, +} from '.'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { loggerMock } from '@kbn/logging-mocks'; const postPagerdutyMock = postPagerduty as jest.Mock; - -const ACTION_TYPE_ID = '.pagerduty'; - const services: Services = actionsMock.createServices(); +const mockedLogger: jest.Mocked = loggerMock.create(); -let actionType: PagerDutyActionType; -let mockedLogger: jest.Mocked; -let configurationUtilities: ActionsConfigurationUtilities; - -beforeAll(() => { - const { logger, actionTypeRegistry } = createActionTypeRegistry(); - actionType = actionTypeRegistry.get< - ActionTypeConfigType, - ActionTypeSecretsType, - ActionParamsType - >(ACTION_TYPE_ID); - mockedLogger = logger; - configurationUtilities = actionTypeRegistry.getUtils(); +let connectorType: PagerDutyConnectorType; +let configurationUtilities: jest.Mocked; + +beforeEach(() => { + configurationUtilities = actionsConfigMock.create(); + connectorType = getConnectorType({ + logger: mockedLogger, + }); }); describe('get()', () => { - test('should return correct action type', () => { - expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('PagerDuty'); + test('should return correct connector type', () => { + expect(connectorType.id).toEqual('.pagerduty'); + expect(connectorType.name).toEqual('PagerDuty'); }); }); describe('validateConfig()', () => { test('should validate and pass when config is valid', () => { - expect(validateConfig(actionType, {}, { configurationUtilities })).toEqual({ apiUrl: null }); + expect(validateConfig(connectorType, {}, { configurationUtilities })).toEqual({ apiUrl: null }); expect( validateConfig( - actionType, + connectorType, { apiUrl: 'bar', }, @@ -72,7 +62,7 @@ describe('validateConfig()', () => { test('should validate and throw error when config is invalid', () => { expect(() => { - validateConfig(actionType, { shouldNotBeHere: true }, { configurationUtilities }); + validateConfig(connectorType, { shouldNotBeHere: true }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type config: [shouldNotBeHere]: definition for this key is missing"` ); @@ -85,14 +75,13 @@ describe('validateConfig()', () => { expect(url).toEqual('https://events.pagerduty.com/v2/enqueue'); }, }; - actionType = getActionType({ + connectorType = getConnectorType({ logger: mockedLogger, - configurationUtilities: configUtils, }); expect( validateConfig( - actionType, + connectorType, { apiUrl: 'https://events.pagerduty.com/v2/enqueue' }, { configurationUtilities: configUtils } ) @@ -106,14 +95,13 @@ describe('validateConfig()', () => { throw new Error(`target url is not added to allowedHosts`); }, }; - actionType = getActionType({ + connectorType = getConnectorType({ logger: mockedLogger, - configurationUtilities: configUtils, }); expect(() => { validateConfig( - actionType, + connectorType, { apiUrl: 'https://events.pagerduty.com/v2/enqueue' }, { configurationUtilities: configUtils } ); @@ -126,20 +114,20 @@ describe('validateConfig()', () => { describe('validateSecrets()', () => { test('should validate and pass when secrets is valid', () => { const routingKey = 'super-secret'; - expect(validateSecrets(actionType, { routingKey }, { configurationUtilities })).toEqual({ + expect(validateSecrets(connectorType, { routingKey }, { configurationUtilities })).toEqual({ routingKey, }); }); test('should validate and throw error when secrets is invalid', () => { expect(() => { - validateSecrets(actionType, { routingKey: false }, { configurationUtilities }); + validateSecrets(connectorType, { routingKey: false }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: [routingKey]: expected value of type [string] but got [boolean]"` ); expect(() => { - validateSecrets(actionType, {}, { configurationUtilities }); + validateSecrets(connectorType, {}, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: [routingKey]: expected value of type [string] but got [undefined]"` ); @@ -148,7 +136,7 @@ describe('validateSecrets()', () => { describe('validateParams()', () => { test('should validate and pass when params is valid', () => { - expect(validateParams(actionType, {}, { configurationUtilities })).toEqual({}); + expect(validateParams(connectorType, {}, { configurationUtilities })).toEqual({}); const params = { eventAction: 'trigger', @@ -161,12 +149,12 @@ describe('validateParams()', () => { group: 'a group', class: 'a class', }; - expect(validateParams(actionType, params, { configurationUtilities })).toEqual(params); + expect(validateParams(connectorType, params, { configurationUtilities })).toEqual(params); }); test('should validate and throw error when params is invalid', () => { expect(() => { - validateParams(actionType, { eventAction: 'ackynollage' }, { configurationUtilities }); + validateParams(connectorType, { eventAction: 'ackynollage' }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot(` "error validating action params: [eventAction]: types that failed validation: - [eventAction.0]: expected value to equal [trigger] @@ -180,7 +168,7 @@ describe('validateParams()', () => { const timestamp = ` ${randoDate}`; expect( validateParams( - actionType, + connectorType, { timestamp, }, @@ -193,7 +181,7 @@ describe('validateParams()', () => { const timestamp = ''; expect( validateParams( - actionType, + connectorType, { timestamp, }, @@ -209,7 +197,7 @@ describe('validateParams()', () => { expect( validateParams( - actionType, + connectorType, { timestamp: date1, }, @@ -218,7 +206,7 @@ describe('validateParams()', () => { ).toEqual({ timestamp: date1 }); expect( validateParams( - actionType, + connectorType, { timestamp: date2, }, @@ -227,7 +215,7 @@ describe('validateParams()', () => { ).toEqual({ timestamp: date2 }); expect( validateParams( - actionType, + connectorType, { timestamp: date3, }, @@ -240,7 +228,7 @@ describe('validateParams()', () => { const timestamp = `1963-09-55 90:23:45`; expect(() => { validateParams( - actionType, + connectorType, { timestamp, }, @@ -252,7 +240,7 @@ describe('validateParams()', () => { test('should validate and throw error when dedupKey is missing on resolve', () => { expect(() => { validateParams( - actionType, + connectorType, { eventAction: 'resolve', }, @@ -279,14 +267,15 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: PagerDutyActionTypeExecutorOptions = { + const executorOptions: PagerDutyConnectorTypeExecutorOptions = { actionId, config, params, secrets, services, + configurationUtilities, }; - const actionResponse = await actionType.executor(executorOptions); + const actionResponse = await connectorType.executor(executorOptions); const { apiUrl, data, headers } = postPagerdutyMock.mock.calls[0][0]; expect({ apiUrl, data, headers }).toMatchInlineSnapshot(` Object { @@ -339,14 +328,15 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: PagerDutyActionTypeExecutorOptions = { + const executorOptions: PagerDutyConnectorTypeExecutorOptions = { actionId, config, params, secrets, services, + configurationUtilities, }; - const actionResponse = await actionType.executor(executorOptions); + const actionResponse = await connectorType.executor(executorOptions); const { apiUrl, data, headers } = postPagerdutyMock.mock.calls[0][0]; expect({ apiUrl, data, headers }).toMatchInlineSnapshot(` Object { @@ -404,14 +394,15 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: PagerDutyActionTypeExecutorOptions = { + const executorOptions: PagerDutyConnectorTypeExecutorOptions = { actionId, config, params, secrets, services, + configurationUtilities, }; - const actionResponse = await actionType.executor(executorOptions); + const actionResponse = await connectorType.executor(executorOptions); const { apiUrl, data, headers } = postPagerdutyMock.mock.calls[0][0]; expect({ apiUrl, data, headers }).toMatchInlineSnapshot(` Object { @@ -460,14 +451,15 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: PagerDutyActionTypeExecutorOptions = { + const executorOptions: PagerDutyConnectorTypeExecutorOptions = { actionId, config, params, secrets, services, + configurationUtilities, }; - const actionResponse = await actionType.executor(executorOptions); + const actionResponse = await connectorType.executor(executorOptions); const { apiUrl, data, headers } = postPagerdutyMock.mock.calls[0][0]; expect({ apiUrl, data, headers }).toMatchInlineSnapshot(` Object { @@ -501,14 +493,15 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: PagerDutyActionTypeExecutorOptions = { + const executorOptions: PagerDutyConnectorTypeExecutorOptions = { actionId, config, params, secrets, services, + configurationUtilities, }; - const actionResponse = await actionType.executor(executorOptions); + const actionResponse = await connectorType.executor(executorOptions); expect(actionResponse).toMatchInlineSnapshot(` Object { "actionId": "some-action-id", @@ -529,14 +522,15 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: PagerDutyActionTypeExecutorOptions = { + const executorOptions: PagerDutyConnectorTypeExecutorOptions = { actionId, config, params, secrets, services, + configurationUtilities, }; - const actionResponse = await actionType.executor(executorOptions); + const actionResponse = await connectorType.executor(executorOptions); expect(actionResponse).toMatchInlineSnapshot(` Object { "actionId": "some-action-id", @@ -557,14 +551,15 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: PagerDutyActionTypeExecutorOptions = { + const executorOptions: PagerDutyConnectorTypeExecutorOptions = { actionId, config, params, secrets, services, + configurationUtilities, }; - const actionResponse = await actionType.executor(executorOptions); + const actionResponse = await connectorType.executor(executorOptions); expect(actionResponse).toMatchInlineSnapshot(` Object { "actionId": "some-action-id", @@ -585,14 +580,15 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: PagerDutyActionTypeExecutorOptions = { + const executorOptions: PagerDutyConnectorTypeExecutorOptions = { actionId, config, params, secrets, services, + configurationUtilities, }; - const actionResponse = await actionType.executor(executorOptions); + const actionResponse = await connectorType.executor(executorOptions); expect(actionResponse).toMatchInlineSnapshot(` Object { "actionId": "some-action-id", @@ -623,14 +619,15 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: PagerDutyActionTypeExecutorOptions = { + const executorOptions: PagerDutyConnectorTypeExecutorOptions = { actionId, config, params, secrets, services, + configurationUtilities, }; - const actionResponse = await actionType.executor(executorOptions); + const actionResponse = await connectorType.executor(executorOptions); const { apiUrl, data, headers } = postPagerdutyMock.mock.calls[0][0]; expect({ apiUrl, data, headers }).toMatchInlineSnapshot(` Object { @@ -684,14 +681,15 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: PagerDutyActionTypeExecutorOptions = { + const executorOptions: PagerDutyConnectorTypeExecutorOptions = { actionId, config, params, secrets, services, + configurationUtilities, }; - const actionResponse = await actionType.executor(executorOptions); + const actionResponse = await connectorType.executor(executorOptions); const { apiUrl, data, headers } = postPagerdutyMock.mock.calls[0][0]; expect({ apiUrl, data, headers }).toMatchInlineSnapshot(` Object { @@ -748,14 +746,15 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: PagerDutyActionTypeExecutorOptions = { + const executorOptions: PagerDutyConnectorTypeExecutorOptions = { actionId, config, params, secrets, services, + configurationUtilities, }; - const actionResponse = await actionType.executor(executorOptions); + const actionResponse = await connectorType.executor(executorOptions); const { apiUrl, data, headers } = postPagerdutyMock.mock.calls[0][0]; expect({ apiUrl, data, headers }).toMatchInlineSnapshot(` Object { @@ -811,14 +810,15 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: PagerDutyActionTypeExecutorOptions = { + const executorOptions: PagerDutyConnectorTypeExecutorOptions = { actionId, config, params, secrets, services, + configurationUtilities, }; - const actionResponse = await actionType.executor(executorOptions); + const actionResponse = await connectorType.executor(executorOptions); const { apiUrl, data, headers } = postPagerdutyMock.mock.calls[0][0]; expect({ apiUrl, data, headers }).toMatchInlineSnapshot(` Object { diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/pagerduty/index.ts similarity index 79% rename from x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/pagerduty/index.ts index da30adf2b3672..e09f6159d26d8 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/pagerduty/index.ts @@ -10,39 +10,38 @@ import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; import moment from 'moment'; import { Logger } from '@kbn/core/server'; -import { postPagerduty } from './lib/post_pagerduty'; -import { - ActionType, - ActionTypeExecutorOptions, - ActionTypeExecutorResult, +import type { + ActionType as ConnectorType, + ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, + ActionTypeExecutorResult as ConnectorTypeExecutorResult, ValidatorServices, -} from '../types'; -import { ActionsConfigurationUtilities } from '../actions_config'; +} from '@kbn/actions-plugin/server/types'; import { AlertingConnectorFeatureId, UptimeConnectorFeatureId, SecurityConnectorFeatureId, -} from '../../common'; +} from '@kbn/actions-plugin/common/types'; +import { postPagerduty } from './post_pagerduty'; // uses the PagerDuty Events API v2 // https://v2.developer.pagerduty.com/docs/events-api-v2 const PAGER_DUTY_API_URL = 'https://events.pagerduty.com/v2/enqueue'; -export type PagerDutyActionType = ActionType< - ActionTypeConfigType, - ActionTypeSecretsType, +export type PagerDutyConnectorType = ConnectorType< + ConnectorTypeConfigType, + ConnectorTypeSecretsType, ActionParamsType, unknown >; -export type PagerDutyActionTypeExecutorOptions = ActionTypeExecutorOptions< - ActionTypeConfigType, - ActionTypeSecretsType, +export type PagerDutyConnectorTypeExecutorOptions = ConnectorTypeExecutorOptions< + ConnectorTypeConfigType, + ConnectorTypeSecretsType, ActionParamsType >; // config definition -export type ActionTypeConfigType = TypeOf; +export type ConnectorTypeConfigType = TypeOf; const configSchemaProps = { apiUrl: schema.nullable(schema.string()), @@ -50,7 +49,7 @@ const configSchemaProps = { const ConfigSchema = schema.object(configSchemaProps); // secrets definition -export type ActionTypeSecretsType = TypeOf; +export type ConnectorTypeSecretsType = TypeOf; const SecretsSchema = schema.object({ routingKey: schema.string(), @@ -110,7 +109,7 @@ function validateParams(paramsObject: unknown): string | void { try { const date = moment(validatedTimestamp); if (!date.isValid()) { - return i18n.translate('xpack.actions.builtin.pagerduty.invalidTimestampErrorMessage', { + return i18n.translate('xpack.stackConnectors.pagerduty.invalidTimestampErrorMessage', { defaultMessage: `error parsing timestamp "{timestamp}"`, values: { timestamp, @@ -118,7 +117,7 @@ function validateParams(paramsObject: unknown): string | void { }); } } catch (err) { - return i18n.translate('xpack.actions.builtin.pagerduty.timestampParsingFailedErrorMessage', { + return i18n.translate('xpack.stackConnectors.pagerduty.timestampParsingFailedErrorMessage', { defaultMessage: `error parsing timestamp "{timestamp}": {message}`, values: { timestamp, @@ -128,7 +127,7 @@ function validateParams(paramsObject: unknown): string | void { } } if (eventAction && EVENT_ACTIONS_WITH_REQUIRED_DEDUPKEY.has(eventAction) && !dedupKey) { - return i18n.translate('xpack.actions.builtin.pagerduty.missingDedupkeyErrorMessage', { + return i18n.translate('xpack.stackConnectors.pagerduty.missingDedupkeyErrorMessage', { defaultMessage: `DedupKey is required when eventAction is "{eventAction}"`, values: { eventAction, @@ -137,19 +136,13 @@ function validateParams(paramsObject: unknown): string | void { } } -export const ActionTypeId = '.pagerduty'; -// action type definition -export function getActionType({ - logger, - configurationUtilities, -}: { - logger: Logger; - configurationUtilities: ActionsConfigurationUtilities; -}): PagerDutyActionType { +export const ConnectorTypeId = '.pagerduty'; +// connector type definition +export function getConnectorType({ logger }: { logger: Logger }): PagerDutyConnectorType { return { - id: ActionTypeId, + id: ConnectorTypeId, minimumLicenseRequired: 'gold', - name: i18n.translate('xpack.actions.builtin.pagerdutyTitle', { + name: i18n.translate('xpack.stackConnectors.pagerduty.title', { defaultMessage: 'PagerDuty', }), supportedFeatureIds: [ @@ -160,7 +153,7 @@ export function getActionType({ validate: { config: { schema: ConfigSchema, - customValidator: validateActionTypeConfig, + customValidator: validateConnectorTypeConfig, }, secrets: { schema: SecretsSchema, @@ -169,12 +162,12 @@ export function getActionType({ schema: ParamsSchema, }, }, - executor: curry(executor)({ logger, configurationUtilities }), + executor: curry(executor)({ logger }), }; } -function validateActionTypeConfig( - configObject: ActionTypeConfigType, +function validateConnectorTypeConfig( + configObject: ConnectorTypeConfigType, validatorServices: ValidatorServices ) { const { configurationUtilities } = validatorServices; @@ -182,7 +175,7 @@ function validateActionTypeConfig( configurationUtilities.ensureUriAllowed(getPagerDutyApiUrl(configObject)); } catch (allowListError) { throw new Error( - i18n.translate('xpack.actions.builtin.pagerduty.pagerdutyConfigurationError', { + i18n.translate('xpack.stackConnectors.pagerduty.configurationError', { defaultMessage: 'error configuring pagerduty action: {message}', values: { message: allowListError.message, @@ -192,24 +185,22 @@ function validateActionTypeConfig( } } -function getPagerDutyApiUrl(config: ActionTypeConfigType): string { +function getPagerDutyApiUrl(config: ConnectorTypeConfigType): string { return config.apiUrl || PAGER_DUTY_API_URL; } // action executor async function executor( - { - logger, - configurationUtilities, - }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, - execOptions: PagerDutyActionTypeExecutorOptions -): Promise> { + { logger }: { logger: Logger }, + execOptions: PagerDutyConnectorTypeExecutorOptions +): Promise> { const actionId = execOptions.actionId; const config = execOptions.config; const secrets = execOptions.secrets; const params = execOptions.params; const services = execOptions.services; + const configurationUtilities = execOptions.configurationUtilities; const apiUrl = getPagerDutyApiUrl(config); const headers = { @@ -226,7 +217,7 @@ async function executor( configurationUtilities ); } catch (err) { - const message = i18n.translate('xpack.actions.builtin.pagerduty.postingErrorMessage', { + const message = i18n.translate('xpack.stackConnectors.pagerduty.postingErrorMessage', { defaultMessage: 'error posting pagerduty event', }); logger.warn(`error thrown posting pagerduty event: ${err.message}`); @@ -249,7 +240,7 @@ async function executor( } if (response.status === 429 || response.status >= 500) { - const message = i18n.translate('xpack.actions.builtin.pagerduty.postingRetryErrorMessage', { + const message = i18n.translate('xpack.stackConnectors.pagerduty.postingRetryErrorMessage', { defaultMessage: 'error posting pagerduty event: http status {status}, retry later', values: { status: response.status, @@ -264,7 +255,7 @@ async function executor( }; } - const message = i18n.translate('xpack.actions.builtin.pagerduty.postingUnexpectedErrorMessage', { + const message = i18n.translate('xpack.stackConnectors.pagerduty.postingUnexpectedErrorMessage', { defaultMessage: 'error posting pagerduty event: unexpected status {status}', values: { status: response.status, diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/pagerduty/post_pagerduty.ts similarity index 81% rename from x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/pagerduty/post_pagerduty.ts index 5aa46703d5ff4..0ef41637967d2 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/pagerduty/post_pagerduty.ts @@ -7,9 +7,9 @@ import axios, { AxiosResponse } from 'axios'; import { Logger } from '@kbn/core/server'; -import { Services } from '../../types'; -import { request } from '../../lib/axios_utils'; -import { ActionsConfigurationUtilities } from '../../actions_config'; +import { Services } from '@kbn/actions-plugin/server/types'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; interface PostPagerdutyOptions { apiUrl: string; diff --git a/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/server_log/index.test.ts similarity index 62% rename from x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/server_log/index.test.ts index 89db7e3d10b84..04c8f9e562f79 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/server_log/index.test.ts @@ -5,35 +5,30 @@ * 2.0. */ -import { validateParams } from '../lib'; +import { validateParams } from '@kbn/actions-plugin/server/lib'; import { Logger } from '@kbn/core/server'; -import { createActionTypeRegistry } from './index.test'; -import { actionsMock } from '../mocks'; -import { - ActionParamsType, - ServerLogActionType, - ServerLogActionTypeExecutorOptions, -} from './server_log'; -import { ActionsConfigurationUtilities } from '../actions_config'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; +import { getConnectorType, ServerLogConnectorType, ServerLogConnectorTypeExecutorOptions } from '.'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { loggerMock } from '@kbn/logging-mocks'; -const ACTION_TYPE_ID = '.server-log'; +const mockedLogger: jest.Mocked = loggerMock.create(); -let actionType: ServerLogActionType; -let mockedLogger: jest.Mocked; -let configurationUtilities: ActionsConfigurationUtilities; +let connectorType: ServerLogConnectorType; +let configurationUtilities: jest.Mocked; -beforeAll(() => { - const { logger, actionTypeRegistry } = createActionTypeRegistry(); - actionType = actionTypeRegistry.get<{}, {}, ActionParamsType>(ACTION_TYPE_ID); - mockedLogger = logger; - configurationUtilities = actionTypeRegistry.getUtils(); - expect(actionType).toBeTruthy(); +beforeEach(() => { + configurationUtilities = actionsConfigMock.create(); + connectorType = getConnectorType({ + logger: mockedLogger, + }); }); -describe('get()', () => { - test('returns action type', () => { - expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('Server log'); +describe('connectorType', () => { + test('returns connector type', () => { + expect(connectorType.id).toEqual('.server-log'); + expect(connectorType.name).toEqual('Server log'); }); }); @@ -41,7 +36,7 @@ describe('validateParams()', () => { test('should validate and pass when params is valid', () => { expect( validateParams( - actionType, + connectorType, { message: 'a message', level: 'info' }, { configurationUtilities } ) @@ -51,7 +46,7 @@ describe('validateParams()', () => { }); expect( validateParams( - actionType, + connectorType, { message: 'a message', level: 'info', @@ -66,19 +61,19 @@ describe('validateParams()', () => { test('should validate and throw error when params is invalid', () => { expect(() => { - validateParams(actionType, {}, { configurationUtilities }); + validateParams(connectorType, {}, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action params: [message]: expected value of type [string] but got [undefined]"` ); expect(() => { - validateParams(actionType, { message: 1 }, { configurationUtilities }); + validateParams(connectorType, { message: 1 }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action params: [message]: expected value of type [string] but got [number]"` ); expect(() => { - validateParams(actionType, { message: 'x', level: 2 }, { configurationUtilities }); + validateParams(connectorType, { message: 'x', level: 2 }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot(` "error validating action params: [level]: types that failed validation: - [level.0]: expected value to equal [trace] @@ -90,7 +85,7 @@ describe('validateParams()', () => { `); expect(() => { - validateParams(actionType, { message: 'x', level: 'foo' }, { configurationUtilities }); + validateParams(connectorType, { message: 'x', level: 'foo' }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot(` "error validating action params: [level]: types that failed validation: - [level.0]: expected value to equal [trace] @@ -106,14 +101,15 @@ describe('validateParams()', () => { describe('execute()', () => { test('calls the executor with proper params', async () => { const actionId = 'some-id'; - const executorOptions: ServerLogActionTypeExecutorOptions = { + const executorOptions: ServerLogConnectorTypeExecutorOptions = { actionId, services: actionsMock.createServices(), params: { message: 'message text here', level: 'info' }, config: {}, secrets: {}, + configurationUtilities, }; - await actionType.executor(executorOptions); + await connectorType.executor(executorOptions); expect(mockedLogger.info).toHaveBeenCalledWith('Server log: message text here'); }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/server_log.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/server_log/index.ts similarity index 63% rename from x-pack/plugins/actions/server/builtin_action_types/server_log.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/server_log/index.ts index e33894e6099d7..23bfe27466349 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/server_log.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/server_log/index.ts @@ -10,12 +10,19 @@ import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; import { Logger, LogMeta } from '@kbn/core/server'; -import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; -import { withoutControlCharacters } from './lib/string_utils'; -import { AlertingConnectorFeatureId, UptimeConnectorFeatureId } from '../../common'; +import type { + ActionType as ConnectorType, + ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, + ActionTypeExecutorResult as ConnectorTypeExecutorResult, +} from '@kbn/actions-plugin/server/types'; +import { + AlertingConnectorFeatureId, + UptimeConnectorFeatureId, +} from '@kbn/actions-plugin/common/connector_feature_config'; +import { withoutControlCharacters } from '../../lib/string_utils'; -export type ServerLogActionType = ActionType<{}, {}, ActionParamsType>; -export type ServerLogActionTypeExecutorOptions = ActionTypeExecutorOptions< +export type ServerLogConnectorType = ConnectorType<{}, {}, ActionParamsType>; +export type ServerLogConnectorTypeExecutorOptions = ConnectorTypeExecutorOptions< {}, {}, ActionParamsType @@ -40,13 +47,13 @@ const ParamsSchema = schema.object({ ), }); -export const ActionTypeId = '.server-log'; -// action type definition -export function getActionType({ logger }: { logger: Logger }): ServerLogActionType { +export const ConnectorTypeId = '.server-log'; +// connector type definition +export function getConnectorType({ logger }: { logger: Logger }): ServerLogConnectorType { return { - id: ActionTypeId, + id: ConnectorTypeId, minimumLicenseRequired: 'basic', - name: i18n.translate('xpack.actions.builtin.serverLogTitle', { + name: i18n.translate('xpack.stackConnectors.serverLog.title', { defaultMessage: 'Server log', }), supportedFeatureIds: [AlertingConnectorFeatureId, UptimeConnectorFeatureId], @@ -63,8 +70,8 @@ export function getActionType({ logger }: { logger: Logger }): ServerLogActionTy async function executor( { logger }: { logger: Logger }, - execOptions: ServerLogActionTypeExecutorOptions -): Promise> { + execOptions: ServerLogConnectorTypeExecutorOptions +): Promise> { const actionId = execOptions.actionId; const params = execOptions.params; @@ -72,7 +79,7 @@ async function executor( try { (logger[params.level] as Logger['info'])(`Server log: ${sanitizedMessage}`); } catch (err) { - const message = i18n.translate('xpack.actions.builtin.serverLog.errorLoggingErrorMessage', { + const message = i18n.translate('xpack.stackConnectors.serverLog.errorLoggingErrorMessage', { defaultMessage: 'error logging message', }); return { diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/slack/index.test.ts similarity index 77% rename from x-pack/plugins/actions/server/builtin_action_types/slack.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/slack/index.test.ts index f400e00db52d6..742cfd7ed1f60 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/slack/index.test.ts @@ -6,13 +6,21 @@ */ import { Logger } from '@kbn/core/server'; -import { Services, ActionTypeExecutorResult } from '../types'; -import { validateParams, validateSecrets } from '../lib'; -import { getActionType, SlackActionType, SlackActionTypeExecutorOptions } from './slack'; -import { actionsConfigMock } from '../actions_config.mock'; -import { actionsMock } from '../mocks'; -import { createActionTypeRegistry } from './index.test'; -import { ActionsConfigurationUtilities } from '../actions_config'; +import { + Services, + ActionTypeExecutorResult as ConnectorTypeExecutorResult, +} from '@kbn/actions-plugin/server/types'; +import { validateParams, validateSecrets } from '@kbn/actions-plugin/server/lib'; +import { + getConnectorType, + SlackConnectorType, + SlackConnectorTypeExecutorOptions, + ConnectorTypeId, +} from '.'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { loggerMock } from '@kbn/logging-mocks'; jest.mock('@slack/webhook', () => { return { @@ -22,39 +30,33 @@ jest.mock('@slack/webhook', () => { }; }); -const ACTION_TYPE_ID = '.slack'; - const services: Services = actionsMock.createServices(); +const mockedLogger: jest.Mocked = loggerMock.create(); -let actionType: SlackActionType; -let mockedLogger: jest.Mocked; +let connectorType: SlackConnectorType; let configurationUtilities: jest.Mocked; -beforeAll(() => { - const { logger } = createActionTypeRegistry(); +beforeEach(() => { configurationUtilities = actionsConfigMock.create(); - actionType = getActionType({ + connectorType = getConnectorType({ async executor(options) { return { status: 'ok', actionId: options.actionId }; }, - configurationUtilities, - logger, + logger: mockedLogger, }); - mockedLogger = logger; - expect(actionType).toBeTruthy(); }); -describe('action registeration', () => { - test('returns action type', () => { - expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('Slack'); +describe('connector registration', () => { + test('returns connector type', () => { + expect(connectorType.id).toEqual(ConnectorTypeId); + expect(connectorType.name).toEqual('Slack'); }); }); describe('validateParams()', () => { test('should validate and pass when params is valid', () => { expect( - validateParams(actionType, { message: 'a message' }, { configurationUtilities }) + validateParams(connectorType, { message: 'a message' }, { configurationUtilities }) ).toEqual({ message: 'a message', }); @@ -62,23 +64,23 @@ describe('validateParams()', () => { test('should validate and throw error when params is invalid', () => { expect(() => { - validateParams(actionType, {}, { configurationUtilities }); + validateParams(connectorType, {}, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action params: [message]: expected value of type [string] but got [undefined]"` ); expect(() => { - validateParams(actionType, { message: 1 }, { configurationUtilities }); + validateParams(connectorType, { message: 1 }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action params: [message]: expected value of type [string] but got [number]"` ); }); }); -describe('validateActionTypeSecrets()', () => { +describe('validateConnectorTypeSecrets()', () => { test('should validate and pass when config is valid', () => { validateSecrets( - actionType, + connectorType, { webhookUrl: 'https://example.com', }, @@ -88,19 +90,19 @@ describe('validateActionTypeSecrets()', () => { test('should validate and throw error when config is invalid', () => { expect(() => { - validateSecrets(actionType, {}, { configurationUtilities }); + validateSecrets(connectorType, {}, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: [webhookUrl]: expected value of type [string] but got [undefined]"` ); expect(() => { - validateSecrets(actionType, { webhookUrl: 1 }, { configurationUtilities }); + validateSecrets(connectorType, { webhookUrl: 1 }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: [webhookUrl]: expected value of type [string] but got [number]"` ); expect(() => { - validateSecrets(actionType, { webhookUrl: 'fee-fi-fo-fum' }, { configurationUtilities }); + validateSecrets(connectorType, { webhookUrl: 'fee-fi-fo-fum' }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: error configuring slack action: unable to parse host name from webhookUrl"` ); @@ -113,14 +115,10 @@ describe('validateActionTypeSecrets()', () => { expect(url).toEqual('https://api.slack.com/'); }, }; - actionType = getActionType({ - logger: mockedLogger, - configurationUtilities: configUtils, - }); expect( validateSecrets( - actionType, + connectorType, { webhookUrl: 'https://api.slack.com/' }, { configurationUtilities: configUtils } ) @@ -136,14 +134,10 @@ describe('validateActionTypeSecrets()', () => { throw new Error(`target hostname is not added to allowedHosts`); }, }; - actionType = getActionType({ - logger: mockedLogger, - configurationUtilities: configUtils, - }); expect(() => { validateSecrets( - actionType, + connectorType, { webhookUrl: 'https://api.slack.com/' }, { configurationUtilities: configUtils } ); @@ -154,8 +148,9 @@ describe('validateActionTypeSecrets()', () => { }); describe('execute()', () => { - beforeAll(() => { - async function mockSlackExecutor(options: SlackActionTypeExecutorOptions) { + beforeEach(() => { + jest.resetAllMocks(); + async function mockSlackExecutor(options: SlackConnectorTypeExecutorOptions) { const { params } = options; const { message } = params; if (message == null) throw new Error('message property required in parameter'); @@ -170,23 +165,23 @@ describe('execute()', () => { text: `slack mockExecutor success: ${message}`, actionId: '', status: 'ok', - } as ActionTypeExecutorResult; + } as ConnectorTypeExecutorResult; } - actionType = getActionType({ + connectorType = getConnectorType({ executor: mockSlackExecutor, logger: mockedLogger, - configurationUtilities: actionsConfigMock.create(), }); }); test('calls the mock executor with success', async () => { - const response = await actionType.executor({ + const response = await connectorType.executor({ actionId: 'some-id', services, config: {}, secrets: { webhookUrl: 'http://example.com' }, params: { message: 'this invocation should succeed' }, + configurationUtilities, }); expect(response).toMatchInlineSnapshot(` Object { @@ -199,12 +194,13 @@ describe('execute()', () => { test('calls the mock executor with failure', async () => { await expect( - actionType.executor({ + connectorType.executor({ actionId: 'some-id', services, config: {}, secrets: { webhookUrl: 'http://example.com' }, params: { message: 'failure: this invocation should fail' }, + configurationUtilities, }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"slack mockExecutor failure: this invocation should fail"` @@ -221,16 +217,16 @@ describe('execute()', () => { proxyBypassHosts: undefined, proxyOnlyHosts: undefined, }); - const actionTypeProxy = getActionType({ + const connectorTypeProxy = getConnectorType({ logger: mockedLogger, - configurationUtilities: configUtils, }); - await actionTypeProxy.executor({ + await connectorTypeProxy.executor({ actionId: 'some-id', services, config: {}, secrets: { webhookUrl: 'http://example.com' }, params: { message: 'this invocation should succeed' }, + configurationUtilities: configUtils, }); expect(mockedLogger.debug).toHaveBeenCalledWith( 'IncomingWebhook was called with proxyUrl https://someproxyhost' @@ -248,16 +244,16 @@ describe('execute()', () => { proxyBypassHosts: new Set(['example.com']), proxyOnlyHosts: undefined, }); - const actionTypeProxy = getActionType({ + const connectorTypeProxy = getConnectorType({ logger: mockedLogger, - configurationUtilities: configUtils, }); - await actionTypeProxy.executor({ + await connectorTypeProxy.executor({ actionId: 'some-id', services, config: {}, secrets: { webhookUrl: 'http://example.com' }, params: { message: 'this invocation should succeed' }, + configurationUtilities: configUtils, }); expect(mockedLogger.debug).not.toHaveBeenCalledWith( 'IncomingWebhook was called with proxyUrl https://someproxyhost' @@ -275,16 +271,16 @@ describe('execute()', () => { proxyBypassHosts: new Set(['not-example.com']), proxyOnlyHosts: undefined, }); - const actionTypeProxy = getActionType({ + const connectorTypeProxy = getConnectorType({ logger: mockedLogger, - configurationUtilities: configUtils, }); - await actionTypeProxy.executor({ + await connectorTypeProxy.executor({ actionId: 'some-id', services, config: {}, secrets: { webhookUrl: 'http://example.com' }, params: { message: 'this invocation should succeed' }, + configurationUtilities: configUtils, }); expect(mockedLogger.debug).toHaveBeenCalledWith( 'IncomingWebhook was called with proxyUrl https://someproxyhost' @@ -302,16 +298,16 @@ describe('execute()', () => { proxyBypassHosts: undefined, proxyOnlyHosts: new Set(['example.com']), }); - const actionTypeProxy = getActionType({ + const connectorTypeProxy = getConnectorType({ logger: mockedLogger, - configurationUtilities: configUtils, }); - await actionTypeProxy.executor({ + await connectorTypeProxy.executor({ actionId: 'some-id', services, config: {}, secrets: { webhookUrl: 'http://example.com' }, params: { message: 'this invocation should succeed' }, + configurationUtilities: configUtils, }); expect(mockedLogger.debug).toHaveBeenCalledWith( 'IncomingWebhook was called with proxyUrl https://someproxyhost' @@ -329,16 +325,16 @@ describe('execute()', () => { proxyBypassHosts: undefined, proxyOnlyHosts: new Set(['not-example.com']), }); - const actionTypeProxy = getActionType({ + const connectorTypeProxy = getConnectorType({ logger: mockedLogger, - configurationUtilities: configUtils, }); - await actionTypeProxy.executor({ + await connectorTypeProxy.executor({ actionId: 'some-id', services, config: {}, secrets: { webhookUrl: 'http://example.com' }, params: { message: 'this invocation should succeed' }, + configurationUtilities: configUtils, }); expect(mockedLogger.debug).not.toHaveBeenCalledWith( 'IncomingWebhook was called with proxyUrl https://someproxyhost' @@ -346,14 +342,14 @@ describe('execute()', () => { }); test('renders parameter templates as expected', async () => { - expect(actionType.renderParameterTemplates).toBeTruthy(); + expect(connectorType.renderParameterTemplates).toBeTruthy(); const paramsWithTemplates = { message: '{{rogue}}', }; const variables = { rogue: '*bold*', }; - const params = actionType.renderParameterTemplates!(paramsWithTemplates, variables); + const params = connectorType.renderParameterTemplates!(paramsWithTemplates, variables); expect(params.message).toBe('`*bold*`'); }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/slack/index.ts similarity index 71% rename from x-pack/plugins/actions/server/builtin_action_types/slack.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/slack/index.ts index 48f1fb5fc2a82..343d353443f51 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/slack/index.ts @@ -15,34 +15,37 @@ import { IncomingWebhook, IncomingWebhookResult } from '@slack/webhook'; import { pipe } from 'fp-ts/lib/pipeable'; import { map, getOrElse } from 'fp-ts/lib/Option'; import { Logger } from '@kbn/core/server'; -import { getRetryAfterIntervalFromHeaders } from './lib/http_rersponse_retry_header'; -import { renderMustacheString } from '../lib/mustache_renderer'; - -import { - ActionType, - ActionTypeExecutorOptions, - ActionTypeExecutorResult, +import type { + ActionType as ConnectorType, + ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, + ActionTypeExecutorResult as ConnectorTypeExecutorResult, ExecutorType, ValidatorServices, -} from '../types'; -import { ActionsConfigurationUtilities } from '../actions_config'; -import { getCustomAgents } from './lib/get_custom_agents'; +} from '@kbn/actions-plugin/server/types'; import { AlertingConnectorFeatureId, UptimeConnectorFeatureId, SecurityConnectorFeatureId, -} from '../../common'; +} from '@kbn/actions-plugin/common/types'; +import { renderMustacheString } from '@kbn/actions-plugin/server/lib/mustache_renderer'; +import { getCustomAgents } from '@kbn/actions-plugin/server/lib/get_custom_agents'; +import { getRetryAfterIntervalFromHeaders } from '../../lib/http_response_retry_header'; -export type SlackActionType = ActionType<{}, ActionTypeSecretsType, ActionParamsType, unknown>; -export type SlackActionTypeExecutorOptions = ActionTypeExecutorOptions< +export type SlackConnectorType = ConnectorType< + {}, + ConnectorTypeSecretsType, + ActionParamsType, + unknown +>; +export type SlackConnectorTypeExecutorOptions = ConnectorTypeExecutorOptions< {}, - ActionTypeSecretsType, + ConnectorTypeSecretsType, ActionParamsType >; // secrets definition -export type ActionTypeSecretsType = TypeOf; +export type ConnectorTypeSecretsType = TypeOf; const secretsSchemaProps = { webhookUrl: schema.string(), @@ -57,23 +60,21 @@ const ParamsSchema = schema.object({ message: schema.string({ minLength: 1 }), }); -// action type definition +// connector type definition -export const ActionTypeId = '.slack'; +export const ConnectorTypeId = '.slack'; // customizing executor is only used for tests -export function getActionType({ +export function getConnectorType({ logger, - configurationUtilities, - executor = curry(slackExecutor)({ logger, configurationUtilities }), + executor = curry(slackExecutor)({ logger }), }: { logger: Logger; - configurationUtilities: ActionsConfigurationUtilities; - executor?: ExecutorType<{}, ActionTypeSecretsType, ActionParamsType, unknown>; -}): SlackActionType { + executor?: ExecutorType<{}, ConnectorTypeSecretsType, ActionParamsType, unknown>; +}): SlackConnectorType { return { - id: ActionTypeId, + id: ConnectorTypeId, minimumLicenseRequired: 'gold', - name: i18n.translate('xpack.actions.builtin.slackTitle', { + name: i18n.translate('xpack.stackConnectors.slack.title', { defaultMessage: 'Slack', }), supportedFeatureIds: [ @@ -84,7 +85,7 @@ export function getActionType({ validate: { secrets: { schema: SecretsSchema, - customValidator: validateActionTypeConfig, + customValidator: validateConnectorTypeConfig, }, params: { schema: ParamsSchema, @@ -104,8 +105,8 @@ function renderParameterTemplates( }; } -function validateActionTypeConfig( - secretsObject: ActionTypeSecretsType, +function validateConnectorTypeConfig( + secretsObject: ConnectorTypeSecretsType, validatorServices: ValidatorServices ) { const { configurationUtilities } = validatorServices; @@ -114,7 +115,7 @@ function validateActionTypeConfig( new URL(configuredUrl); } catch (err) { throw new Error( - i18n.translate('xpack.actions.builtin.slack.slackConfigurationErrorNoHostname', { + i18n.translate('xpack.stackConnectors.slack.configurationErrorNoHostname', { defaultMessage: 'error configuring slack action: unable to parse host name from webhookUrl', }) ); @@ -124,7 +125,7 @@ function validateActionTypeConfig( configurationUtilities.ensureUriAllowed(configuredUrl); } catch (allowListError) { throw new Error( - i18n.translate('xpack.actions.builtin.slack.slackConfigurationError', { + i18n.translate('xpack.stackConnectors.slack.configurationError', { defaultMessage: 'error configuring slack action: {message}', values: { message: allowListError.message, @@ -137,15 +138,13 @@ function validateActionTypeConfig( // action executor async function slackExecutor( - { - logger, - configurationUtilities, - }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, - execOptions: SlackActionTypeExecutorOptions -): Promise> { + { logger }: { logger: Logger }, + execOptions: SlackConnectorTypeExecutorOptions +): Promise> { const actionId = execOptions.actionId; const secrets = execOptions.secrets; const params = execOptions.params; + const configurationUtilities = execOptions.configurationUtilities; let result: IncomingWebhookResult; const { webhookUrl } = secrets; @@ -192,7 +191,7 @@ async function slackExecutor( } const errMessage = i18n.translate( - 'xpack.actions.builtin.slack.unexpectedHttpResponseErrorMessage', + 'xpack.stackConnectors.slack.unexpectedHttpResponseErrorMessage', { defaultMessage: 'unexpected http response from slack: {httpStatus} {httpStatusText}', values: { @@ -208,7 +207,7 @@ async function slackExecutor( if (result == null) { const errMessage = i18n.translate( - 'xpack.actions.builtin.slack.unexpectedNullResponseErrorMessage', + 'xpack.stackConnectors.slack.unexpectedNullResponseErrorMessage', { defaultMessage: 'unexpected null response from slack', } @@ -223,11 +222,11 @@ async function slackExecutor( return successResult(actionId, result); } -function successResult(actionId: string, data: unknown): ActionTypeExecutorResult { +function successResult(actionId: string, data: unknown): ConnectorTypeExecutorResult { return { status: 'ok', data, actionId }; } -function errorResult(actionId: string, message: string): ActionTypeExecutorResult { +function errorResult(actionId: string, message: string): ConnectorTypeExecutorResult { return { status: 'error', message, @@ -237,8 +236,8 @@ function errorResult(actionId: string, message: string): ActionTypeExecutorResul function serviceErrorResult( actionId: string, serviceMessage: string -): ActionTypeExecutorResult { - const errMessage = i18n.translate('xpack.actions.builtin.slack.errorPostingErrorMessage', { +): ConnectorTypeExecutorResult { + const errMessage = i18n.translate('xpack.stackConnectors.slack.errorPostingErrorMessage', { defaultMessage: 'error posting slack message', }); return { @@ -249,9 +248,9 @@ function serviceErrorResult( }; } -function retryResult(actionId: string, message: string): ActionTypeExecutorResult { +function retryResult(actionId: string, message: string): ConnectorTypeExecutorResult { const errMessage = i18n.translate( - 'xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage', + 'xpack.stackConnectors.slack.errorPostingRetryLaterErrorMessage', { defaultMessage: 'error posting a slack message, retry later', } @@ -268,12 +267,12 @@ function retryResultSeconds( actionId: string, message: string, retryAfter: number -): ActionTypeExecutorResult { +): ConnectorTypeExecutorResult { const retryEpoch = Date.now() + retryAfter * 1000; const retry = new Date(retryEpoch); const retryString = retry.toISOString(); const errMessage = i18n.translate( - 'xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage', + 'xpack.stackConnectors.slack.errorPostingRetryDateErrorMessage', { defaultMessage: 'error posting a slack message, retry at {retryString}', values: { diff --git a/x-pack/plugins/actions/server/builtin_action_types/teams.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/teams/index.test.ts similarity index 74% rename from x-pack/plugins/actions/server/builtin_action_types/teams.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/teams/index.test.ts index 6b5dd3fb0d50f..a45db6021bfd7 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/teams.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/teams/index.test.ts @@ -6,19 +6,19 @@ */ import { Logger } from '@kbn/core/server'; -import { Services } from '../types'; -import { validateParams, validateSecrets } from '../lib'; +import { Services } from '@kbn/actions-plugin/server/types'; +import { validateParams, validateSecrets } from '@kbn/actions-plugin/server/lib'; import axios from 'axios'; -import { ActionParamsType, ActionTypeSecretsType, getActionType, TeamsActionType } from './teams'; -import { actionsConfigMock } from '../actions_config.mock'; -import { actionsMock } from '../mocks'; -import { createActionTypeRegistry } from './index.test'; -import * as utils from '../lib/axios_utils'; -import { ActionsConfigurationUtilities } from '../actions_config'; +import { getConnectorType, TeamsConnectorType, ConnectorTypeId } from '.'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; +import * as utils from '@kbn/actions-plugin/server/lib/axios_utils'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; +import { loggerMock } from '@kbn/logging-mocks'; jest.mock('axios'); -jest.mock('../lib/axios_utils', () => { - const originalUtils = jest.requireActual('../lib/axios_utils'); +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(), @@ -27,34 +27,32 @@ jest.mock('../lib/axios_utils', () => { }); axios.create = jest.fn(() => axios); -const requestMock = utils.request as jest.Mock; - -const ACTION_TYPE_ID = '.teams'; +const requestMock = utils.request as jest.Mock; const services: Services = actionsMock.createServices(); +const mockedLogger: jest.Mocked = loggerMock.create(); -let actionType: TeamsActionType; -let mockedLogger: jest.Mocked; -let configurationUtilities: ActionsConfigurationUtilities; +let connectorType: TeamsConnectorType; +let configurationUtilities: jest.Mocked; -beforeAll(() => { - const { logger, actionTypeRegistry } = createActionTypeRegistry(); - actionType = actionTypeRegistry.get<{}, ActionTypeSecretsType, ActionParamsType>(ACTION_TYPE_ID); - mockedLogger = logger; - configurationUtilities = actionTypeRegistry.getUtils(); +beforeEach(() => { + configurationUtilities = actionsConfigMock.create(); + connectorType = getConnectorType({ + logger: mockedLogger, + }); }); -describe('action registration', () => { - test('returns action type', () => { - expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('Microsoft Teams'); +describe('connector registration', () => { + test('returns connector type', () => { + expect(connectorType.id).toEqual(ConnectorTypeId); + expect(connectorType.name).toEqual('Microsoft Teams'); }); }); describe('validateParams()', () => { test('should validate and pass when params is valid', () => { expect( - validateParams(actionType, { message: 'a message' }, { configurationUtilities }) + validateParams(connectorType, { message: 'a message' }, { configurationUtilities }) ).toEqual({ message: 'a message', }); @@ -62,13 +60,13 @@ describe('validateParams()', () => { test('should validate and throw error when params is invalid', () => { expect(() => { - validateParams(actionType, {}, { configurationUtilities }); + validateParams(connectorType, {}, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action params: [message]: expected value of type [string] but got [undefined]"` ); expect(() => { - validateParams(actionType, { message: 1 }, { configurationUtilities }); + validateParams(connectorType, { message: 1 }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action params: [message]: expected value of type [string] but got [number]"` ); @@ -78,7 +76,7 @@ describe('validateParams()', () => { describe('validateActionTypeSecrets()', () => { test('should validate and pass when config is valid', () => { validateSecrets( - actionType, + connectorType, { webhookUrl: 'https://example.com', }, @@ -88,19 +86,19 @@ describe('validateActionTypeSecrets()', () => { test('should validate and throw error when config is invalid', () => { expect(() => { - validateSecrets(actionType, {}, { configurationUtilities }); + validateSecrets(connectorType, {}, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: [webhookUrl]: expected value of type [string] but got [undefined]"` ); expect(() => { - validateSecrets(actionType, { webhookUrl: 1 }, { configurationUtilities }); + validateSecrets(connectorType, { webhookUrl: 1 }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: [webhookUrl]: expected value of type [string] but got [number]"` ); expect(() => { - validateSecrets(actionType, { webhookUrl: 'fee-fi-fo-fum' }, { configurationUtilities }); + validateSecrets(connectorType, { webhookUrl: 'fee-fi-fo-fum' }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: error configuring teams action: unable to parse host name from webhookUrl"` ); @@ -113,14 +111,10 @@ describe('validateActionTypeSecrets()', () => { expect(url).toEqual('https://outlook.office.com/'); }, }; - actionType = getActionType({ - logger: mockedLogger, - configurationUtilities: configUtils, - }); expect( validateSecrets( - actionType, + connectorType, { webhookUrl: 'https://outlook.office.com/' }, { configurationUtilities: configUtils } ) @@ -136,14 +130,10 @@ describe('validateActionTypeSecrets()', () => { throw new Error(`target hostname is not added to allowedHosts`); }, }; - actionType = getActionType({ - logger: mockedLogger, - configurationUtilities: configUtils, - }); expect(() => { validateSecrets( - actionType, + connectorType, { webhookUrl: 'https://outlook.office.com/' }, { configurationUtilities: configUtils } ); @@ -156,13 +146,10 @@ describe('validateActionTypeSecrets()', () => { describe('execute()', () => { beforeAll(() => { requestMock.mockReset(); - actionType = getActionType({ - logger: mockedLogger, - configurationUtilities: actionsConfigMock.create(), - }); }); beforeEach(() => { + jest.resetAllMocks(); requestMock.mockReset(); requestMock.mockResolvedValue({ status: 200, @@ -174,12 +161,13 @@ describe('execute()', () => { }); test('calls the mock executor with success', async () => { - const response = await actionType.executor({ + const response = await connectorType.executor({ actionId: 'some-id', services, config: {}, secrets: { webhookUrl: 'http://example.com' }, params: { message: 'this invocation should succeed' }, + configurationUtilities, }); delete requestMock.mock.calls[0][0].configurationUtilities; expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` @@ -227,12 +215,13 @@ describe('execute()', () => { }); test('calls the mock executor with success proxy', async () => { - const response = await actionType.executor({ + const response = await connectorType.executor({ actionId: 'some-id', services, config: {}, secrets: { webhookUrl: 'http://example.com' }, params: { message: 'this invocation should succeed' }, + configurationUtilities, }); delete requestMock.mock.calls[0][0].configurationUtilities; expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` diff --git a/x-pack/plugins/actions/server/builtin_action_types/teams.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/teams/index.ts similarity index 71% rename from x-pack/plugins/actions/server/builtin_action_types/teams.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/teams/index.ts index 490c52f8f901a..3405f59a68f7a 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/teams.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/teams/index.ts @@ -13,32 +13,36 @@ 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 { getRetryAfterIntervalFromHeaders } from './lib/http_rersponse_retry_header'; -import { isOk, promiseResult, Result } from './lib/result_type'; -import { request } from '../lib/axios_utils'; -import { - ActionType, - ActionTypeExecutorOptions, - ActionTypeExecutorResult, +import type { + ActionType as ConnectorType, + ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, + ActionTypeExecutorResult as ConnectorTypeExecutorResult, ValidatorServices, -} from '../types'; -import { ActionsConfigurationUtilities } from '../actions_config'; +} from '@kbn/actions-plugin/server/types'; +import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; import { AlertingConnectorFeatureId, UptimeConnectorFeatureId, SecurityConnectorFeatureId, -} from '../../common'; +} from '@kbn/actions-plugin/common/types'; +import { getRetryAfterIntervalFromHeaders } from '../../lib/http_response_retry_header'; +import { isOk, promiseResult, Result } from '../../lib/result_type'; -export type TeamsActionType = ActionType<{}, ActionTypeSecretsType, ActionParamsType, unknown>; -export type TeamsActionTypeExecutorOptions = ActionTypeExecutorOptions< +export type TeamsConnectorType = ConnectorType< + {}, + ConnectorTypeSecretsType, + ActionParamsType, + unknown +>; +export type TeamsConnectorTypeExecutorOptions = ConnectorTypeExecutorOptions< {}, - ActionTypeSecretsType, + ConnectorTypeSecretsType, ActionParamsType >; // secrets definition -export type ActionTypeSecretsType = TypeOf; +export type ConnectorTypeSecretsType = TypeOf; const secretsSchemaProps = { webhookUrl: schema.string(), @@ -53,19 +57,13 @@ const ParamsSchema = schema.object({ message: schema.string({ minLength: 1 }), }); -export const ActionTypeId = '.teams'; -// action type definition -export function getActionType({ - logger, - configurationUtilities, -}: { - logger: Logger; - configurationUtilities: ActionsConfigurationUtilities; -}): TeamsActionType { +export const ConnectorTypeId = '.teams'; +// connector type definition +export function getConnectorType({ logger }: { logger: Logger }): TeamsConnectorType { return { - id: ActionTypeId, + id: ConnectorTypeId, minimumLicenseRequired: 'gold', - name: i18n.translate('xpack.actions.builtin.teamsTitle', { + name: i18n.translate('xpack.stackConnectors.teams.title', { defaultMessage: 'Microsoft Teams', }), supportedFeatureIds: [ @@ -76,18 +74,18 @@ export function getActionType({ validate: { secrets: { schema: SecretsSchema, - customValidator: validateActionTypeConfig, + customValidator: validateConnectorTypeConfig, }, params: { schema: ParamsSchema, }, }, - executor: curry(teamsExecutor)({ logger, configurationUtilities }), + executor: curry(teamsExecutor)({ logger }), }; } -function validateActionTypeConfig( - secretsObject: ActionTypeSecretsType, +function validateConnectorTypeConfig( + secretsObject: ConnectorTypeSecretsType, validatorServices: ValidatorServices ) { const { configurationUtilities } = validatorServices; @@ -96,7 +94,7 @@ function validateActionTypeConfig( new URL(configuredUrl); } catch (err) { throw new Error( - i18n.translate('xpack.actions.builtin.teams.teamsConfigurationErrorNoHostname', { + i18n.translate('xpack.stackConnectors.teams.configurationErrorNoHostname', { defaultMessage: 'error configuring teams action: unable to parse host name from webhookUrl', }) ); @@ -106,7 +104,7 @@ function validateActionTypeConfig( configurationUtilities.ensureUriAllowed(configuredUrl); } catch (allowListError) { throw new Error( - i18n.translate('xpack.actions.builtin.teams.teamsConfigurationError', { + i18n.translate('xpack.stackConnectors.teams.configurationError', { defaultMessage: 'error configuring teams action: {message}', values: { message: allowListError.message, @@ -119,15 +117,13 @@ function validateActionTypeConfig( // action executor async function teamsExecutor( - { - logger, - configurationUtilities, - }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, - execOptions: TeamsActionTypeExecutorOptions -): Promise> { + { logger }: { logger: Logger }, + execOptions: TeamsConnectorTypeExecutorOptions +): Promise> { const actionId = execOptions.actionId; const secrets = execOptions.secrets; const params = execOptions.params; + const configurationUtilities = execOptions.configurationUtilities; const { webhookUrl } = secrets; const { message } = params; const data = { text: message }; @@ -185,12 +181,12 @@ async function teamsExecutor( } } -function successResult(actionId: string, data: unknown): ActionTypeExecutorResult { +function successResult(actionId: string, data: unknown): ConnectorTypeExecutorResult { return { status: 'ok', data, actionId }; } -function errorResultUnexpectedError(actionId: string): ActionTypeExecutorResult { - const errMessage = i18n.translate('xpack.actions.builtin.teams.unreachableErrorMessage', { +function errorResultUnexpectedError(actionId: string): ConnectorTypeExecutorResult { + const errMessage = i18n.translate('xpack.stackConnectors.teams.unreachableErrorMessage', { defaultMessage: 'error posting to Microsoft Teams, unexpected error', }); return { @@ -203,8 +199,8 @@ function errorResultUnexpectedError(actionId: string): ActionTypeExecutorResult< function errorResultInvalid( actionId: string, serviceMessage: string -): ActionTypeExecutorResult { - const errMessage = i18n.translate('xpack.actions.builtin.teams.invalidResponseErrorMessage', { +): ConnectorTypeExecutorResult { + const errMessage = i18n.translate('xpack.stackConnectors.teams.invalidResponseErrorMessage', { defaultMessage: 'error posting to Microsoft Teams, invalid response', }); return { @@ -215,9 +211,9 @@ function errorResultInvalid( }; } -function retryResult(actionId: string, message: string): ActionTypeExecutorResult { +function retryResult(actionId: string, message: string): ConnectorTypeExecutorResult { const errMessage = i18n.translate( - 'xpack.actions.builtin.teams.errorPostingRetryLaterErrorMessage', + 'xpack.stackConnectors.teams.errorPostingRetryLaterErrorMessage', { defaultMessage: 'error posting a Microsoft Teams message, retry later', } @@ -234,12 +230,12 @@ function retryResultSeconds( actionId: string, message: string, retryAfter: number -): ActionTypeExecutorResult { +): ConnectorTypeExecutorResult { const retryEpoch = Date.now() + retryAfter * 1000; const retry = new Date(retryEpoch); const retryString = retry.toISOString(); const errMessage = i18n.translate( - 'xpack.actions.builtin.teams.errorPostingRetryDateErrorMessage', + 'xpack.stackConnectors.teams.errorPostingRetryDateErrorMessage', { defaultMessage: 'error posting a Microsoft Teams message, retry at {retryString}', values: { diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/webhook/index.test.ts similarity index 76% rename from x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/webhook/index.test.ts index c374a25d1088b..5d82212e67179 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/webhook/index.test.ts @@ -5,28 +5,27 @@ * 2.0. */ -import { Services } from '../types'; -import { validateConfig, validateSecrets, validateParams } from '../lib'; -import { actionsConfigMock } from '../actions_config.mock'; -import { createActionTypeRegistry } from './index.test'; +import { Services } from '@kbn/actions-plugin/server/types'; +import { validateConfig, validateParams, validateSecrets } from '@kbn/actions-plugin/server/lib'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; import { Logger } from '@kbn/core/server'; -import { actionsMock } from '../mocks'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; import axios from 'axios'; import { - ActionParamsType, - ActionTypeConfigType, - ActionTypeSecretsType, - getActionType, - WebhookActionType, + ConnectorTypeConfigType, + ConnectorTypeSecretsType, + getConnectorType, + WebhookConnectorType, WebhookMethods, -} from './webhook'; +} from '.'; -import * as utils from '../lib/axios_utils'; -import { ActionsConfigurationUtilities } from '../actions_config'; +import * as utils from '@kbn/actions-plugin/server/lib/axios_utils'; +import { loggerMock } from '@kbn/logging-mocks'; jest.mock('axios'); -jest.mock('../lib/axios_utils', () => { - const originalUtils = jest.requireActual('../lib/axios_utils'); +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(), @@ -39,29 +38,23 @@ const requestMock = utils.request as jest.Mock; axios.create = jest.fn(() => axios); -const ACTION_TYPE_ID = '.webhook'; - const services: Services = actionsMock.createServices(); +const mockedLogger: jest.Mocked = loggerMock.create(); + +let connectorType: WebhookConnectorType; +let configurationUtilities: jest.Mocked; -let actionType: WebhookActionType; -let mockedLogger: jest.Mocked; -let configurationUtilities: ActionsConfigurationUtilities; - -beforeAll(() => { - const { logger, actionTypeRegistry } = createActionTypeRegistry(); - actionType = actionTypeRegistry.get< - ActionTypeConfigType, - ActionTypeSecretsType, - ActionParamsType - >(ACTION_TYPE_ID); - mockedLogger = logger; - configurationUtilities = actionTypeRegistry.getUtils(); +beforeEach(() => { + configurationUtilities = actionsConfigMock.create(); + connectorType = getConnectorType({ + logger: mockedLogger, + }); }); -describe('actionType', () => { - test('exposes the action as `webhook` on its Id and Name', () => { - expect(actionType.id).toEqual('.webhook'); - expect(actionType.name).toEqual('Webhook'); +describe('connectorType', () => { + test('exposes the connector as `webhook` on its Id and Name', () => { + expect(connectorType.id).toEqual('.webhook'); + expect(connectorType.name).toEqual('Webhook'); }); }); @@ -71,19 +64,19 @@ describe('secrets validation', () => { user: 'bob', password: 'supersecret', }; - expect(validateSecrets(actionType, secrets, { configurationUtilities })).toEqual(secrets); + expect(validateSecrets(connectorType, secrets, { configurationUtilities })).toEqual(secrets); }); test('fails when secret user is provided, but password is omitted', () => { expect(() => { - validateSecrets(actionType, { user: 'bob' }, { configurationUtilities }); + validateSecrets(connectorType, { user: 'bob' }, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type secrets: both user and password must be specified"` ); }); test('succeeds when basic authentication credentials are omitted', () => { - expect(validateSecrets(actionType, {}, { configurationUtilities })).toEqual({ + expect(validateSecrets(connectorType, {}, { configurationUtilities })).toEqual({ password: null, user: null, }); @@ -101,7 +94,7 @@ describe('config validation', () => { url: 'http://mylisteningserver:9200/endpoint', hasAuth: true, }; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ ...defaultValues, ...config, }); @@ -114,7 +107,7 @@ describe('config validation', () => { method, hasAuth: true, }; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ ...defaultValues, ...config, }); @@ -127,7 +120,7 @@ describe('config validation', () => { method: 'https', }; expect(() => { - validateConfig(actionType, config, { configurationUtilities }); + validateConfig(connectorType, config, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot(` "error validating action type config: [method]: types that failed validation: - [method.0]: expected value to equal [post] @@ -140,7 +133,7 @@ describe('config validation', () => { url: 'http://mylisteningserver:9200/endpoint', hasAuth: true, }; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ ...defaultValues, ...config, }); @@ -151,7 +144,7 @@ describe('config validation', () => { url: 'example.com/do-something', }; expect(() => { - validateConfig(actionType, config, { configurationUtilities }); + validateConfig(connectorType, config, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot( '"error validating action type config: error configuring webhook action: unable to parse url: TypeError: Invalid URL: example.com/do-something"' ); @@ -167,7 +160,7 @@ describe('config validation', () => { }, hasAuth: true, }; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ ...defaultValues, ...config, }); @@ -179,7 +172,7 @@ describe('config validation', () => { headers: 'application/json', }; expect(() => { - validateConfig(actionType, config, { configurationUtilities }); + validateConfig(connectorType, config, { configurationUtilities }); }).toThrowErrorMatchingInlineSnapshot(` "error validating action type config: [headers]: types that failed validation: - [headers.0]: could not parse record value from json input @@ -198,7 +191,7 @@ describe('config validation', () => { hasAuth: true, }; - expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ ...defaultValues, ...config, }); @@ -211,10 +204,6 @@ describe('config validation', () => { throw new Error(`target url is not present in allowedHosts`); }, }; - actionType = getActionType({ - logger: mockedLogger, - configurationUtilities: configUtils, - }); // any for testing // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -226,7 +215,7 @@ describe('config validation', () => { }; expect(() => { - validateConfig(actionType, config, { configurationUtilities: configUtils }); + validateConfig(connectorType, config, { configurationUtilities: configUtils }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action type config: error configuring webhook action: target url is not present in allowedHosts"` ); @@ -236,14 +225,14 @@ describe('config validation', () => { describe('params validation', () => { test('param validation passes when no fields are provided as none are required', () => { const params: Record = {}; - expect(validateParams(actionType, params, { configurationUtilities })).toEqual({}); + expect(validateParams(connectorType, params, { configurationUtilities })).toEqual({}); }); test('params validation passes when a valid body is provided', () => { const params: Record = { body: 'count: {{ctx.payload.hits.total}}', }; - expect(validateParams(actionType, params, { configurationUtilities })).toEqual({ + expect(validateParams(connectorType, params, { configurationUtilities })).toEqual({ ...params, }); }); @@ -252,13 +241,10 @@ describe('params validation', () => { describe('execute()', () => { beforeAll(() => { requestMock.mockReset(); - actionType = getActionType({ - logger: mockedLogger, - configurationUtilities: actionsConfigMock.create(), - }); }); beforeEach(() => { + jest.resetAllMocks(); requestMock.mockReset(); requestMock.mockResolvedValue({ status: 200, @@ -270,7 +256,7 @@ describe('execute()', () => { }); test('execute with username/password sends request with basic auth', async () => { - const config: ActionTypeConfigType = { + const config: ConnectorTypeConfigType = { url: 'https://abc.def/my-webhook', method: WebhookMethods.POST, headers: { @@ -278,12 +264,13 @@ describe('execute()', () => { }, hasAuth: true, }; - await actionType.executor({ + await connectorType.executor({ actionId: 'some-id', services, config, secrets: { user: 'abc', password: '123' }, params: { body: 'some data' }, + configurationUtilities, }); delete requestMock.mock.calls[0][0].configurationUtilities; @@ -328,7 +315,7 @@ describe('execute()', () => { }); test('execute with exception maxContentLength size exceeded should log the proper error', async () => { - const config: ActionTypeConfigType = { + const config: ConnectorTypeConfigType = { url: 'https://abc.def/my-webhook', method: WebhookMethods.POST, headers: { @@ -342,12 +329,13 @@ describe('execute()', () => { isAxiosError: true, message: 'maxContentLength size of 1000000 exceeded', }); - await actionType.executor({ + await connectorType.executor({ actionId: 'some-id', services, config, secrets: { user: 'abc', password: '123' }, params: { body: 'some data' }, + configurationUtilities, }); expect(mockedLogger.error).toBeCalledWith( 'error on some-id webhook event: maxContentLength size of 1000000 exceeded' @@ -355,7 +343,7 @@ describe('execute()', () => { }); test('execute without username/password sends request without basic auth', async () => { - const config: ActionTypeConfigType = { + const config: ConnectorTypeConfigType = { url: 'https://abc.def/my-webhook', method: WebhookMethods.POST, headers: { @@ -363,13 +351,14 @@ describe('execute()', () => { }, hasAuth: false, }; - const secrets: ActionTypeSecretsType = { user: null, password: null }; - await actionType.executor({ + const secrets: ConnectorTypeSecretsType = { user: null, password: null }; + await connectorType.executor({ actionId: 'some-id', services, config, secrets, params: { body: 'some data' }, + configurationUtilities, }); delete requestMock.mock.calls[0][0].configurationUtilities; @@ -412,14 +401,14 @@ describe('execute()', () => { test('renders parameter templates as expected', async () => { const rogue = `double-quote:"; line-break->\n`; - expect(actionType.renderParameterTemplates).toBeTruthy(); + expect(connectorType.renderParameterTemplates).toBeTruthy(); const paramsWithTemplates = { body: '{"x": "{{rogue}}"}', }; const variables = { rogue, }; - const params = actionType.renderParameterTemplates!(paramsWithTemplates, variables); + const params = connectorType.renderParameterTemplates!(paramsWithTemplates, variables); // eslint-disable-next-line @typescript-eslint/no-explicit-any let paramsObject: any; diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/webhook/index.ts similarity index 74% rename from x-pack/plugins/actions/server/builtin_action_types/webhook.ts rename to x-pack/plugins/stack_connectors/server/connector_types/stack/webhook/index.ts index 42454b53ebde8..bc25d65d45915 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/webhook/index.ts @@ -12,23 +12,22 @@ 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 { getRetryAfterIntervalFromHeaders } from './lib/http_rersponse_retry_header'; -import { nullableType } from './lib/nullable'; -import { isOk, promiseResult, Result } from './lib/result_type'; -import { - ActionType, - ActionTypeExecutorOptions, - ActionTypeExecutorResult, +import type { + ActionType as ConnectorType, + ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, + ActionTypeExecutorResult as ConnectorTypeExecutorResult, ValidatorServices, -} from '../types'; -import { ActionsConfigurationUtilities } from '../actions_config'; -import { request } from '../lib/axios_utils'; -import { renderMustacheString } from '../lib/mustache_renderer'; +} from '@kbn/actions-plugin/server/types'; +import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; import { AlertingConnectorFeatureId, UptimeConnectorFeatureId, SecurityConnectorFeatureId, -} from '../../common'; +} from '@kbn/actions-plugin/common/types'; +import { renderMustacheString } from '@kbn/actions-plugin/server/lib/mustache_renderer'; +import { getRetryAfterIntervalFromHeaders } from '../../lib/http_response_retry_header'; +import { nullableType } from '../../lib/nullable'; +import { isOk, promiseResult, Result } from '../../lib/result_type'; // config definition export enum WebhookMethods { @@ -36,15 +35,15 @@ export enum WebhookMethods { PUT = 'put', } -export type WebhookActionType = ActionType< - ActionTypeConfigType, - ActionTypeSecretsType, +export type WebhookConnectorType = ConnectorType< + ConnectorTypeConfigType, + ConnectorTypeSecretsType, ActionParamsType, unknown >; -export type WebhookActionTypeExecutorOptions = ActionTypeExecutorOptions< - ActionTypeConfigType, - ActionTypeSecretsType, +export type WebhookConnectorTypeExecutorOptions = ConnectorTypeExecutorOptions< + ConnectorTypeConfigType, + ConnectorTypeSecretsType, ActionParamsType >; @@ -58,10 +57,10 @@ const configSchemaProps = { hasAuth: schema.boolean({ defaultValue: true }), }; const ConfigSchema = schema.object(configSchemaProps); -export type ActionTypeConfigType = TypeOf; +export type ConnectorTypeConfigType = TypeOf; // secrets definition -export type ActionTypeSecretsType = TypeOf; +export type ConnectorTypeSecretsType = TypeOf; const secretSchemaProps = { user: schema.nullable(schema.string()), password: schema.nullable(schema.string()), @@ -71,7 +70,7 @@ const SecretsSchema = schema.object(secretSchemaProps, { // user and password must be set together (or not at all) if (!secrets.password && !secrets.user) return; if (secrets.password && secrets.user) return; - return i18n.translate('xpack.actions.builtin.webhook.invalidUsernamePassword', { + return i18n.translate('xpack.stackConnectors.webhook.invalidUsernamePassword', { defaultMessage: 'both user and password must be specified', }); }, @@ -83,19 +82,13 @@ const ParamsSchema = schema.object({ body: schema.maybe(schema.string()), }); -export const ActionTypeId = '.webhook'; -// action type definition -export function getActionType({ - logger, - configurationUtilities, -}: { - logger: Logger; - configurationUtilities: ActionsConfigurationUtilities; -}): WebhookActionType { +export const ConnectorTypeId = '.webhook'; +// connector type definition +export function getConnectorType({ logger }: { logger: Logger }): WebhookConnectorType { return { - id: ActionTypeId, + id: ConnectorTypeId, minimumLicenseRequired: 'gold', - name: i18n.translate('xpack.actions.builtin.webhookTitle', { + name: i18n.translate('xpack.stackConnectors.webhook.title', { defaultMessage: 'Webhook', }), supportedFeatureIds: [ @@ -106,7 +99,7 @@ export function getActionType({ validate: { config: { schema: ConfigSchema, - customValidator: validateActionTypeConfig, + customValidator: validateConnectorTypeConfig, }, secrets: { schema: SecretsSchema, @@ -116,7 +109,7 @@ export function getActionType({ }, }, renderParameterTemplates, - executor: curry(executor)({ logger, configurationUtilities }), + executor: curry(executor)({ logger }), }; } @@ -130,8 +123,8 @@ function renderParameterTemplates( }; } -function validateActionTypeConfig( - configObject: ActionTypeConfigType, +function validateConnectorTypeConfig( + configObject: ConnectorTypeConfigType, validatorServices: ValidatorServices ) { const { configurationUtilities } = validatorServices; @@ -140,7 +133,7 @@ function validateActionTypeConfig( new URL(configuredUrl); } catch (err) { throw new Error( - i18n.translate('xpack.actions.builtin.webhook.webhookConfigurationErrorNoHostname', { + i18n.translate('xpack.stackConnectors.webhook.configurationErrorNoHostname', { defaultMessage: 'error configuring webhook action: unable to parse url: {err}', values: { err, @@ -153,7 +146,7 @@ function validateActionTypeConfig( configurationUtilities.ensureUriAllowed(configuredUrl); } catch (allowListError) { throw new Error( - i18n.translate('xpack.actions.builtin.webhook.webhookConfigurationError', { + i18n.translate('xpack.stackConnectors.webhook.configurationError', { defaultMessage: 'error configuring webhook action: {message}', values: { message: allowListError.message, @@ -165,17 +158,15 @@ function validateActionTypeConfig( // action executor export async function executor( - { - logger, - configurationUtilities, - }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, - execOptions: WebhookActionTypeExecutorOptions -): Promise> { + { logger }: { logger: Logger }, + execOptions: WebhookConnectorTypeExecutorOptions +): Promise> { const actionId = execOptions.actionId; const { method, url, headers = {}, hasAuth } = execOptions.config; const { body: data } = execOptions.params; + const configurationUtilities = execOptions.configurationUtilities; - const secrets: ActionTypeSecretsType = execOptions.secrets; + const secrets: ConnectorTypeSecretsType = execOptions.secrets; const basicAuth = hasAuth && isString(secrets.user) && isString(secrets.password) ? { auth: { username: secrets.user, password: secrets.password } } @@ -247,15 +238,15 @@ export async function executor( } // Action Executor Result w/ internationalisation -function successResult(actionId: string, data: unknown): ActionTypeExecutorResult { +function successResult(actionId: string, data: unknown): ConnectorTypeExecutorResult { return { status: 'ok', data, actionId }; } function errorResultInvalid( actionId: string, serviceMessage: string -): ActionTypeExecutorResult { - const errMessage = i18n.translate('xpack.actions.builtin.webhook.invalidResponseErrorMessage', { +): ConnectorTypeExecutorResult { + const errMessage = i18n.translate('xpack.stackConnectors.webhook.invalidResponseErrorMessage', { defaultMessage: 'error calling webhook, invalid response', }); return { @@ -269,8 +260,8 @@ function errorResultInvalid( function errorResultRequestFailed( actionId: string, serviceMessage: string -): ActionTypeExecutorResult { - const errMessage = i18n.translate('xpack.actions.builtin.webhook.requestFailedErrorMessage', { +): ConnectorTypeExecutorResult { + const errMessage = i18n.translate('xpack.stackConnectors.webhook.requestFailedErrorMessage', { defaultMessage: 'error calling webhook, request failed', }); return { @@ -281,8 +272,8 @@ function errorResultRequestFailed( }; } -function errorResultUnexpectedError(actionId: string): ActionTypeExecutorResult { - const errMessage = i18n.translate('xpack.actions.builtin.webhook.unreachableErrorMessage', { +function errorResultUnexpectedError(actionId: string): ConnectorTypeExecutorResult { + const errMessage = i18n.translate('xpack.stackConnectors.webhook.unreachableErrorMessage', { defaultMessage: 'error calling webhook, unexpected error', }); return { @@ -292,9 +283,9 @@ function errorResultUnexpectedError(actionId: string): ActionTypeExecutorResult< }; } -function retryResult(actionId: string, serviceMessage: string): ActionTypeExecutorResult { +function retryResult(actionId: string, serviceMessage: string): ConnectorTypeExecutorResult { const errMessage = i18n.translate( - 'xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage', + 'xpack.stackConnectors.webhook.invalidResponseRetryLaterErrorMessage', { defaultMessage: 'error calling webhook, retry later', } @@ -313,12 +304,12 @@ function retryResultSeconds( serviceMessage: string, retryAfter: number -): ActionTypeExecutorResult { +): ConnectorTypeExecutorResult { const retryEpoch = Date.now() + retryAfter * 1000; const retry = new Date(retryEpoch); const retryString = retry.toISOString(); const errMessage = i18n.translate( - 'xpack.actions.builtin.webhook.invalidResponseRetryDateErrorMessage', + 'xpack.stackConnectors.webhook.invalidResponseRetryDateErrorMessage', { defaultMessage: 'error calling webhook, retry at {retryString}', values: { diff --git a/x-pack/plugins/stack_connectors/server/index.ts b/x-pack/plugins/stack_connectors/server/index.ts new file mode 100644 index 0000000000000..2cc792da9f9a3 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { PluginInitializerContext } from '@kbn/core/server'; +import { StackConnectorsPlugin } from './plugin'; + +export const plugin = (initContext: PluginInitializerContext) => + new StackConnectorsPlugin(initContext); diff --git a/x-pack/plugins/stack_connectors/server/plugin.test.ts b/x-pack/plugins/stack_connectors/server/plugin.test.ts new file mode 100644 index 0000000000000..b28899ecf0865 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/plugin.test.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PluginInitializerContext } from '@kbn/core/server'; +import { coreMock } from '@kbn/core/server/mocks'; +import { StackConnectorsPlugin } from './plugin'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; + +describe('Stack Connectors Plugin', () => { + describe('setup()', () => { + let context: PluginInitializerContext; + let plugin: StackConnectorsPlugin; + let coreSetup: ReturnType; + + beforeEach(() => { + context = coreMock.createPluginInitializerContext(); + plugin = new StackConnectorsPlugin(context); + coreSetup = coreMock.createSetup(); + }); + + it('should register built in connector types', () => { + const actionsSetup = actionsMock.createSetup(); + plugin.setup(coreSetup, { actions: actionsSetup }); + expect(actionsSetup.registerType).toHaveBeenCalledTimes(15); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + id: '.email', + name: 'Email', + }) + ); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + id: '.index', + name: 'Index', + }) + ); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ + id: '.pagerduty', + name: 'PagerDuty', + }) + ); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 4, + expect.objectContaining({ + id: '.swimlane', + name: 'Swimlane', + }) + ); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 5, + expect.objectContaining({ + id: '.server-log', + name: 'Server log', + }) + ); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 6, + expect.objectContaining({ + id: '.slack', + name: 'Slack', + }) + ); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 7, + expect.objectContaining({ + id: '.webhook', + name: 'Webhook', + }) + ); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 8, + expect.objectContaining({ + id: '.cases-webhook', + name: 'Webhook - Case Management', + }) + ); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 9, + expect.objectContaining({ + id: '.xmatters', + name: 'xMatters', + }) + ); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 10, + expect.objectContaining({ + id: '.servicenow', + name: 'ServiceNow ITSM', + }) + ); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 11, + expect.objectContaining({ + id: '.servicenow-sir', + name: 'ServiceNow SecOps', + }) + ); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 12, + expect.objectContaining({ + id: '.servicenow-itom', + name: 'ServiceNow ITOM', + }) + ); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 13, + expect.objectContaining({ + id: '.jira', + name: 'Jira', + }) + ); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 14, + expect.objectContaining({ + id: '.resilient', + name: 'IBM Resilient', + }) + ); + expect(actionsSetup.registerType).toHaveBeenNthCalledWith( + 15, + expect.objectContaining({ + id: '.teams', + name: 'Microsoft Teams', + }) + ); + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/server/plugin.ts b/x-pack/plugins/stack_connectors/server/plugin.ts new file mode 100644 index 0000000000000..0c8b32b49d9f4 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/plugin.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 { PluginInitializerContext, Plugin, CoreSetup, Logger } from '@kbn/core/server'; +import { PluginSetupContract as ActionsPluginSetupContract } from '@kbn/actions-plugin/server'; +import { registerConnectorTypes } from './connector_types'; +import { getWellKnownEmailServiceRoute } from './routes'; +export interface ConnectorsPluginsSetup { + actions: ActionsPluginSetupContract; +} + +export interface ConnectorsPluginsStart { + actions: ActionsPluginSetupContract; +} + +export class StackConnectorsPlugin implements Plugin { + private readonly logger: Logger; + + constructor(context: PluginInitializerContext) { + this.logger = context.logger.get(); + } + + public setup(core: CoreSetup, plugins: ConnectorsPluginsSetup) { + const router = core.http.createRouter(); + const { actions } = plugins; + + getWellKnownEmailServiceRoute(router); + + registerConnectorTypes({ + logger: this.logger, + actions, + publicBaseUrl: core.http.basePath.publicBaseUrl, + }); + } + + public start() {} + public stop() {} +} diff --git a/x-pack/plugins/stack_connectors/server/routes/get_well_known_email_service.test.ts b/x-pack/plugins/stack_connectors/server/routes/get_well_known_email_service.test.ts new file mode 100644 index 0000000000000..ed8d827f6e237 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/routes/get_well_known_email_service.test.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getWellKnownEmailServiceRoute } from './get_well_known_email_service'; +import { httpServiceMock, httpServerMock } from '@kbn/core/server/mocks'; + +describe('getWellKnownEmailServiceRoute', () => { + it('returns config for well known email service', async () => { + const router = httpServiceMock.createRouter(); + + getWellKnownEmailServiceRoute(router); + + const [config, handler] = router.get.mock.calls[0]; + expect(config.path).toMatchInlineSnapshot( + `"/internal/stack_connectors/_email_config/{service}"` + ); + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest({ + params: { service: 'gmail' }, + }); + await handler({}, mockRequest, mockResponse); + + expect(mockResponse.ok).toHaveBeenCalledWith({ + body: { + host: 'smtp.gmail.com', + port: 465, + secure: true, + }, + }); + }); + + it('returns config for elastic cloud email service', async () => { + const router = httpServiceMock.createRouter(); + + getWellKnownEmailServiceRoute(router); + + const [config, handler] = router.get.mock.calls[0]; + expect(config.path).toMatchInlineSnapshot( + `"/internal/stack_connectors/_email_config/{service}"` + ); + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest({ + params: { service: 'elastic_cloud' }, + }); + + await handler({}, mockRequest, mockResponse); + + expect(mockResponse.ok).toHaveBeenCalledWith({ + body: { + host: 'dockerhost', + port: 10025, + secure: false, + }, + }); + }); + + it('returns empty for unknown service', async () => { + const router = httpServiceMock.createRouter(); + + getWellKnownEmailServiceRoute(router); + + const [config, handler] = router.get.mock.calls[0]; + expect(config.path).toMatchInlineSnapshot( + `"/internal/stack_connectors/_email_config/{service}"` + ); + + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest({ + params: { service: 'foo' }, + }); + await handler({}, mockRequest, mockResponse); + + expect(mockResponse.ok).toHaveBeenCalledWith({ + body: {}, + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/server/routes/get_well_known_email_service.ts b/x-pack/plugins/stack_connectors/server/routes/get_well_known_email_service.ts new file mode 100644 index 0000000000000..8606240d60be0 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/routes/get_well_known_email_service.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 { schema } from '@kbn/config-schema'; +import { + IRouter, + RequestHandlerContext, + KibanaRequest, + IKibanaResponse, + KibanaResponseFactory, +} from '@kbn/core/server'; +import nodemailerGetService from 'nodemailer/lib/well-known'; +import SMTPConnection from 'nodemailer/lib/smtp-connection'; +import { AdditionalEmailServices, INTERNAL_BASE_STACK_CONNECTORS_API_PATH } from '../../common'; +import { ELASTIC_CLOUD_SERVICE } from '../connector_types/stack/email'; + +const paramSchema = schema.object({ + service: schema.string(), +}); + +export const getWellKnownEmailServiceRoute = (router: IRouter) => { + router.get( + { + path: `${INTERNAL_BASE_STACK_CONNECTORS_API_PATH}/_email_config/{service}`, + validate: { + params: paramSchema, + }, + }, + handler + ); + + async function handler( + ctx: RequestHandlerContext, + req: KibanaRequest<{ service: string }, unknown, unknown>, + res: KibanaResponseFactory + ): Promise { + const { service } = req.params; + + let response: SMTPConnection.Options = {}; + if (service === AdditionalEmailServices.ELASTIC_CLOUD) { + response = ELASTIC_CLOUD_SERVICE; + } else { + const serviceEntry = nodemailerGetService(service); + if (serviceEntry) { + response = { + host: serviceEntry.host, + port: serviceEntry.port, + secure: serviceEntry.secure, + }; + } + } + + return res.ok({ + body: response, + }); + } +}; diff --git a/x-pack/plugins/stack_connectors/server/routes/index.ts b/x-pack/plugins/stack_connectors/server/routes/index.ts new file mode 100644 index 0000000000000..2766b99679845 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/routes/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 { getWellKnownEmailServiceRoute } from './get_well_known_email_service'; diff --git a/x-pack/plugins/stack_connectors/server/types.ts b/x-pack/plugins/stack_connectors/server/types.ts new file mode 100644 index 0000000000000..01f7768bfac10 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/types.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. + */ + +export type { GetFieldsByIssueTypeResponse as JiraGetFieldsResponse } from './connector_types/cases/jira/types'; +export type { GetCommonFieldsResponse as ServiceNowGetFieldsResponse } from './connector_types/cases/servicenow/types'; +export type { GetCommonFieldsResponse as ResilientGetFieldsResponse } from './connector_types/cases/resilient/types'; +export type { SwimlanePublicConfigurationType } from './connector_types/cases/swimlane/types'; + +export type { + CasesWebhookConnectorTypeId, + CasesWebhookActionParams, + EmailConnectorTypeId, + EmailActionParams, + IndexConnectorTypeId, + IndexActionParams, + PagerDutyConnectorTypeId, + PagerDutyActionParams, + ServerLogConnectorTypeId, + ServerLogActionParams, + SlackConnectorTypeId, + SlackActionParams, + WebhookConnectorTypeId, + WebhookActionParams, + ServiceNowITSMConnectorTypeId, + ServiceNowSIRConnectorTypeId, + ServiceNowActionParams, + JiraConnectorTypeId, + JiraActionParams, + ResilientConnectorTypeId, + ResilientActionParams, + TeamsConnectorTypeId, + TeamsActionParams, +} from './connector_types'; diff --git a/x-pack/plugins/stack_connectors/tsconfig.json b/x-pack/plugins/stack_connectors/tsconfig.json new file mode 100644 index 0000000000000..395dc5a65be86 --- /dev/null +++ b/x-pack/plugins/stack_connectors/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "server/**/*", + // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 + "server/**/*.json", + "common/**/*" + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../actions/tsconfig.json" } + ] +} diff --git a/x-pack/plugins/synthetics/common/formatters/formatters.test.ts b/x-pack/plugins/synthetics/common/formatters/formatters.test.ts new file mode 100644 index 0000000000000..18618665fb23c --- /dev/null +++ b/x-pack/plugins/synthetics/common/formatters/formatters.test.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { formatKibanaNamespace } from './formatters'; + +describe('formatKibanaNamespace', () => { + it('replaces all invalid characters with an underscore', () => { + const kibanaSpaceId = '1a-_'; + expect(formatKibanaNamespace(kibanaSpaceId)).toEqual('1a__'); + }); +}); diff --git a/x-pack/plugins/synthetics/common/formatters/formatters.ts b/x-pack/plugins/synthetics/common/formatters/formatters.ts index ba2351606ab9c..99931e4e24294 100644 --- a/x-pack/plugins/synthetics/common/formatters/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/formatters.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { INVALID_NAMESPACE_CHARACTERS } from '@kbn/fleet-plugin/common'; import { DataStream } from '../runtime_types'; import { httpFormatters, HTTPFormatMap } from './http/formatters'; import { tcpFormatters, TCPFormatMap } from './tcp/formatters'; @@ -35,3 +35,10 @@ export const formatters: Formatters = { ...browserFormatters, ...commonFormatters, }; + +/* Formats kibana space id into a valid Fleet-compliant datastream namespace */ +export const formatKibanaNamespace = (spaceId: string) => { + const namespaceRegExp = new RegExp(INVALID_NAMESPACE_CHARACTERS, 'g'); + const kibanaNamespace = spaceId.replace(namespaceRegExp, '_'); + return kibanaNamespace; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/defaults.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/defaults.tsx index c8412a866dbb7..edef022a2e997 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/defaults.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/defaults.tsx @@ -5,6 +5,7 @@ * 2.0. */ import { isEqual } from 'lodash'; +import { formatKibanaNamespace } from '../../../../../../common/formatters'; import { DEFAULT_FIELDS, DEFAULT_TLS_FIELDS } from '../constants'; import { ConfigKey, @@ -18,6 +19,7 @@ import { export const getDefaultFormFields = ( spaceId: string = 'default' ): Record> => { + const kibanaNamespace = formatKibanaNamespace(spaceId); return { [FormMonitorType.MULTISTEP]: { ...DEFAULT_FIELDS[DataStream.BROWSER], @@ -27,29 +29,29 @@ export const getDefaultFormFields = ( fileName: '', }, [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.MULTISTEP, - [ConfigKey.NAMESPACE]: spaceId, + [ConfigKey.NAMESPACE]: kibanaNamespace, }, [FormMonitorType.SINGLE]: { ...DEFAULT_FIELDS[DataStream.BROWSER], [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.SINGLE, - [ConfigKey.NAMESPACE]: spaceId, + [ConfigKey.NAMESPACE]: kibanaNamespace, }, [FormMonitorType.HTTP]: { ...DEFAULT_FIELDS[DataStream.HTTP], isTLSEnabled: false, [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.HTTP, - [ConfigKey.NAMESPACE]: spaceId, + [ConfigKey.NAMESPACE]: kibanaNamespace, }, [FormMonitorType.TCP]: { ...DEFAULT_FIELDS[DataStream.TCP], isTLSEnabled: false, [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.TCP, - [ConfigKey.NAMESPACE]: spaceId, + [ConfigKey.NAMESPACE]: kibanaNamespace, }, [FormMonitorType.ICMP]: { ...DEFAULT_FIELDS[DataStream.ICMP], [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.ICMP, - [ConfigKey.NAMESPACE]: spaceId, + [ConfigKey.NAMESPACE]: kibanaNamespace, }, }; }; 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 ab91278980af2..83ba909f3063c 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 @@ -8,6 +8,7 @@ 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 { EuiButtonGroup, @@ -527,6 +528,9 @@ export const FIELD: Record = { props: ({ field }) => ({ selectedOptions: field, }), + validation: () => ({ + validate: (namespace) => isValidNamespace(namespace).error, + }), }, [ConfigKey.MAX_REDIRECTS]: { fieldKey: ConfigKey.MAX_REDIRECTS, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/index.ts index c69bb9421ab7c..1bdb1a996cb04 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/index.ts @@ -6,4 +6,4 @@ */ export * from './use_is_edit_flow'; -export * from './use_kibana_space'; +export { useKibanaSpace } from '../../../../../hooks/use_kibana_space'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_add_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_add_page.tsx index 327e7cf574528..651d751902777 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_add_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_add_page.tsx @@ -9,7 +9,7 @@ import React, { useEffect } from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import { useTrackPageview } from '@kbn/observability-plugin/public'; -import { useKibanaSpace } from './hooks/use_kibana_space'; +import { useKibanaSpace } from './hooks'; import { getServiceLocations } from '../../state'; import { MonitorSteps } from './steps'; import { MonitorForm } from './form'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_kibana_space.tsx b/x-pack/plugins/synthetics/public/hooks/use_kibana_space.tsx similarity index 81% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_kibana_space.tsx rename to x-pack/plugins/synthetics/public/hooks/use_kibana_space.tsx index e7e7a6f1375f5..1a4b8e31e1228 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_kibana_space.tsx +++ b/x-pack/plugins/synthetics/public/hooks/use_kibana_space.tsx @@ -4,10 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { Space } from '@kbn/spaces-plugin/common'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useFetcher } from '@kbn/observability-plugin/public'; -import { ClientPluginsStart } from '../../../../../plugin'; +import { ClientPluginsStart } from '../plugin'; export const useKibanaSpace = () => { const { services } = useKibana(); @@ -16,7 +16,7 @@ export const useKibanaSpace = () => { data: space, loading, error, - } = useFetcher(() => { + } = useFetcher>(() => { return services.spaces?.getActiveSpace(); }, [services.spaces]); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/settings/types.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/settings/types.ts index e0ddd911b23da..ad13d23259d99 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/settings/types.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/settings/types.ts @@ -6,24 +6,24 @@ */ import type { - IndexActionTypeId, - JiraActionTypeId, - PagerDutyActionTypeId, - ServerLogActionTypeId, - ServiceNowITSMActionTypeId as ServiceNowActionTypeId, - SlackActionTypeId, - TeamsActionTypeId, - WebhookActionTypeId, - EmailActionTypeId, -} from '@kbn/actions-plugin/server/builtin_action_types'; + IndexConnectorTypeId, + JiraConnectorTypeId, + PagerDutyConnectorTypeId, + ServerLogConnectorTypeId, + ServiceNowITSMConnectorTypeId as ServiceNowConnectorTypeId, + SlackConnectorTypeId, + TeamsConnectorTypeId, + WebhookConnectorTypeId, + EmailConnectorTypeId, +} from '@kbn/stack-connectors-plugin/server/connector_types'; export type ActionTypeId = - | typeof SlackActionTypeId - | typeof PagerDutyActionTypeId - | typeof ServerLogActionTypeId - | typeof IndexActionTypeId - | typeof TeamsActionTypeId - | typeof ServiceNowActionTypeId - | typeof JiraActionTypeId - | typeof WebhookActionTypeId - | typeof EmailActionTypeId; + | typeof SlackConnectorTypeId + | typeof PagerDutyConnectorTypeId + | typeof ServerLogConnectorTypeId + | typeof IndexConnectorTypeId + | typeof TeamsConnectorTypeId + | typeof ServiceNowConnectorTypeId + | typeof JiraConnectorTypeId + | typeof WebhookConnectorTypeId + | typeof EmailConnectorTypeId; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alert_actions.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alert_actions.ts index 8bda332884546..7c04b0ba9ef92 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alert_actions.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/alert_actions.ts @@ -15,7 +15,7 @@ import type { JiraActionParams, WebhookActionParams, EmailActionParams, -} from '@kbn/actions-plugin/server'; +} from '@kbn/stack-connectors-plugin/server/connector_types'; import { NewAlertParams } from './alerts'; import { ACTION_GROUP_DEFINITIONS } from '../../../../common/constants/alerts'; import { MonitorStatusTranslations } from '../../../../common/translations'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts index a13e72801eda1..02857fafb69a9 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/monitor_management.ts @@ -35,9 +35,11 @@ export const setMonitor = async ({ id?: string; }): Promise<{ attributes: { errors: ServiceLocationErrors } } | SyntheticsMonitor> => { if (id) { - return await apiService.put(`${API_URLS.SYNTHETICS_MONITORS}/${id}`, monitor); + return await apiService.put(`${API_URLS.SYNTHETICS_MONITORS}/${id}`); } else { - return await apiService.post(API_URLS.SYNTHETICS_MONITORS, monitor); + return await apiService.post(API_URLS.SYNTHETICS_MONITORS, monitor, undefined, { + preserve_namespace: true, + }); } }; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/utils.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/utils.ts index cbba8cf02cf22..026eb2ec1ffc7 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/utils.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/utils.ts @@ -73,10 +73,11 @@ class ApiService { return response; } - public async post(apiUrl: string, data?: any, decodeType?: any) { + public async post(apiUrl: string, data?: any, decodeType?: any, params?: HttpFetchQuery) { const response = await this._http!.post(apiUrl, { method: 'POST', body: JSON.stringify(data), + query: params, }); this.addInspectorRequest?.({ data: response, status: FETCH_STATUS.SUCCESS, loading: false }); @@ -95,10 +96,11 @@ class ApiService { return response; } - public async put(apiUrl: string, data?: any, decodeType?: any) { + public async put(apiUrl: string, data?: any, decodeType?: any, params?: HttpFetchQuery) { const response = await this._http!.put(apiUrl, { method: 'PUT', body: JSON.stringify(data), + query: params, }); if (decodeType) { diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts index b7caffa1530a6..ee894c1a602e8 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { v4 as uuidV4 } from 'uuid'; import { schema } from '@kbn/config-schema'; import { SavedObject, @@ -11,7 +12,7 @@ import { KibanaRequest, SavedObjectsErrorHelpers, } from '@kbn/core/server'; -import { v4 as uuidV4 } from 'uuid'; +import { isValidNamespace } from '@kbn/fleet-plugin/common'; import { SyntheticsMonitorClient } from '../../synthetics_service/synthetics_monitor/synthetics_monitor_client'; import { ConfigKey, @@ -19,9 +20,13 @@ import { SyntheticsMonitor, EncryptedSyntheticsMonitor, } from '../../../common/runtime_types'; +import { formatKibanaNamespace } from '../../../common/formatters'; import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes/types'; import { API_URLS } from '../../../common/constants'; -import { DEFAULT_FIELDS } from '../../../common/constants/monitor_defaults'; +import { + DEFAULT_FIELDS, + DEFAULT_NAMESPACE_STRING, +} from '../../../common/constants/monitor_defaults'; import { syntheticsMonitorType } from '../../legacy_uptime/lib/saved_objects/synthetics_monitor'; import { validateMonitor } from './monitor_validation'; import { sendTelemetryEvents, formatTelemetryEvent } from '../telemetry/monitor_upgrade_sender'; @@ -36,6 +41,7 @@ export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ body: schema.any(), query: schema.object({ id: schema.maybe(schema.string()), + preserve_namespace: schema.maybe(schema.boolean()), }), }, handler: async ({ @@ -85,6 +91,7 @@ export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ return response.ok({ body: newMonitor }); } catch (getErr) { + server.logger.error(getErr); if (SavedObjectsErrorHelpers.isForbiddenError(getErr)) { return response.forbidden({ body: getErr }); } @@ -139,18 +146,28 @@ export const syncNewMonitor = async ({ request: KibanaRequest; }) => { const newMonitorId = id ?? uuidV4(); + const { preserve_namespace: preserveNamespace } = request.query as Record< + string, + { preserve_namespace?: boolean } + >; let monitorSavedObject: SavedObject | null = null; + const monitorWithNamespace = { + ...normalizedMonitor, + [ConfigKey.NAMESPACE]: preserveNamespace + ? normalizedMonitor[ConfigKey.NAMESPACE] + : getMonitorNamespace(server, request, normalizedMonitor[ConfigKey.NAMESPACE]), + }; try { const newMonitorPromise = createNewSavedObjectMonitor({ - normalizedMonitor, + normalizedMonitor: monitorWithNamespace, id: newMonitorId, savedObjectsClient, }); const syncErrorsPromise = syntheticsMonitorClient.addMonitor( - monitor as MonitorFields, + monitorWithNamespace as MonitorFields, newMonitorId, request, savedObjectsClient @@ -185,7 +202,24 @@ export const syncNewMonitor = async ({ request, }); } + server.logger.error(e); throw e; } }; + +export const getMonitorNamespace = ( + server: UptimeServerSetup, + request: KibanaRequest, + configuredNamespace: string +) => { + const spaceId = server.spaces.spacesService.getSpaceId(request); + const kibanaNamespace = formatKibanaNamespace(spaceId); + const namespace = + configuredNamespace === DEFAULT_NAMESPACE_STRING ? kibanaNamespace : configuredNamespace; + const { error } = isValidNamespace(namespace); + if (error) { + throw new Error(`Cannot save monitor. Monitor namespace is invalid: ${error}`); + } + return namespace; +}; diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts index 0cb817599b165..8b81e34840c66 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts @@ -7,7 +7,6 @@ import { schema } from '@kbn/config-schema'; import { UMServerLibs } from '../../legacy_uptime/lib/lib'; import { ProjectBrowserMonitor } from '../../../common/runtime_types'; - import { SyntheticsStreamingRouteFactory } from '../../legacy_uptime/routes/types'; import { API_URLS } from '../../../common/constants'; import { getAllLocations } from '../../synthetics_service/get_all_locations'; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/normalizers/browser.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/normalizers/browser.test.ts index d2a0d4154e2b6..3daad4962f977 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/normalizers/browser.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/normalizers/browser.test.ts @@ -149,7 +149,7 @@ describe('browser normalizers', () => { params: '', type: 'browser', project_id: projectId, - namespace: 'test-space', + namespace: 'test_space', original_space: 'test-space', custom_heartbeat_id: 'test-id-1-test-project-id-test-space', timeout: null, @@ -201,7 +201,7 @@ describe('browser normalizers', () => { 'throttling.upload_speed': '15', type: 'browser', project_id: projectId, - namespace: 'test-space', + namespace: 'test_space', original_space: 'test-space', custom_heartbeat_id: 'test-id-2-test-project-id-test-space', timeout: null, @@ -258,7 +258,7 @@ describe('browser normalizers', () => { 'throttling.upload_speed': '15', type: 'browser', project_id: projectId, - namespace: 'test-space', + namespace: 'test_space', original_space: 'test-space', custom_heartbeat_id: 'test-id-3-test-project-id-test-space', timeout: null, diff --git a/x-pack/plugins/synthetics/server/synthetics_service/normalizers/browser.ts b/x-pack/plugins/synthetics/server/synthetics_service/normalizers/browser.ts index 5f797f34515cd..a546dcf2d9295 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/normalizers/browser.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/normalizers/browser.ts @@ -6,7 +6,7 @@ */ import { DEFAULT_FIELDS } from '../../../common/constants/monitor_defaults'; - +import { formatKibanaNamespace } from '../../../common/formatters'; import { BrowserFields, ConfigKey, @@ -101,7 +101,7 @@ export const normalizeProjectMonitor = ({ : defaultFields[ConfigKey.PARAMS], [ConfigKey.JOURNEY_FILTERS_MATCH]: monitor.filter?.match || defaultFields[ConfigKey.JOURNEY_FILTERS_MATCH], - [ConfigKey.NAMESPACE]: namespace || defaultFields[ConfigKey.NAMESPACE], + [ConfigKey.NAMESPACE]: formatKibanaNamespace(namespace) || defaultFields[ConfigKey.NAMESPACE], [ConfigKey.ORIGINAL_SPACE]: namespace || defaultFields[ConfigKey.ORIGINAL_SPACE], [ConfigKey.CUSTOM_HEARTBEAT_ID]: `${monitor.id}-${projectId}-${namespace}`, [ConfigKey.TIMEOUT]: null, 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 c3c54c5aab81e..4f4285a6e07a6 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 @@ -68,8 +68,7 @@ export class SyntheticsPrivateLocation { } else { newPolicy.name = `${config[ConfigKey.NAME]}-${locName}-${spaceId}`; } - - newPolicy.namespace = 'default'; + newPolicy.namespace = config[ConfigKey.NAMESPACE]; const { formattedPolicy } = formatSyntheticsPolicy(newPolicy, config.type, { ...(config as Partial), diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts index d6ae8024b92f7..3be60d3f3a31b 100644 --- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts @@ -847,6 +847,63 @@ describe('estimateCapacity', () => { }, }); }); + + test("estimates minutes_to_drain_overdue even if there isn't any overdue task", async () => { + expect( + estimateCapacity( + logger, + mockStats( + { max_workers: 10, poll_interval: 3000 }, + { + overdue: undefined, + owner_ids: 1, + overdue_non_recurring: 0, + capacity_requirements: { + per_minute: 60, + per_hour: 0, + per_day: 0, + }, + }, + { + execution: { + duration: {}, + duration_by_persistence: { + ephemeral: { + p50: 400, + p90: 500, + p95: 1200, + p99: 1500, + }, + non_recurring: { + p50: 400, + p90: 500, + p95: 1200, + p99: 1500, + }, + recurring: { + p50: 400, + p90: 500, + p95: 1200, + p99: 1500, + }, + }, + // no non-recurring executions in the system in recent history + persistence: { + ephemeral: 0, + non_recurring: 0, + recurring: 100, + }, + result_frequency_percent_as_number: {}, + }, + } + ) + ).value.observed + ).toMatchObject({ + observed_kibana_instances: 1, + minutes_to_drain_overdue: 0, + max_throughput_per_minute: 200, + }); + }); }); function mockStats( diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts index 9b48c84da5319..88feea306050c 100644 --- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts +++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts @@ -199,7 +199,7 @@ export function estimateCapacity( max_throughput_per_minute_per_kibana: capacityPerMinutePerKibana, max_throughput_per_minute: assumedCapacityAvailablePerMinute, minutes_to_drain_overdue: - overdue / (assumedKibanaInstances * averageCapacityUsedByPersistedTasksPerKibana), + overdue ?? 0 / (assumedKibanaInstances * averageCapacityUsedByPersistedTasksPerKibana), avg_recurring_required_throughput_per_minute: averageRecurringRequiredPerMinute, avg_recurring_required_throughput_per_minute_per_kibana: assumedAverageRecurringRequiredThroughputPerMinutePerKibana, diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 3cbe97e096ac5..e40e471d1963d 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -1651,6 +1651,55 @@ } } }, + "count_rules_by_execution_status": { + "properties": { + "success": { + "type": "long" + }, + "error": { + "type": "long" + }, + "warning": { + "type": "long" + } + } + }, + "count_rules_with_tags": { + "type": "long" + }, + "count_rules_by_notify_when": { + "properties": { + "on_action_group_change": { + "type": "long" + }, + "on_active_alert": { + "type": "long" + }, + "on_throttle_interval": { + "type": "long" + } + } + }, + "count_rules_snoozed": { + "type": "long" + }, + "count_rules_muted": { + "type": "long" + }, + "count_rules_with_muted_alerts": { + "type": "long" + }, + "count_connector_types_by_consumers": { + "properties": { + "DYNAMIC_KEY": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + } + } + } + } + }, "avg_execution_time_per_day": { "type": "long" }, @@ -2623,216 +2672,6 @@ } } }, - "canvas": { - "properties": { - "workpads": { - "properties": { - "total": { - "type": "long", - "_meta": { - "description": "The total number of Canvas Workpads in the cluster" - } - } - } - }, - "pages": { - "properties": { - "total": { - "type": "long", - "_meta": { - "description": "The total number of pages across all Canvas Workpads" - } - }, - "per_workpad": { - "properties": { - "avg": { - "type": "float", - "_meta": { - "description": "The average number of pages across all Canvas Workpads" - } - }, - "min": { - "type": "long", - "_meta": { - "description": "The minimum number of pages found in a Canvas Workpad" - } - }, - "max": { - "type": "long", - "_meta": { - "description": "The maximum number of pages found in a Canvas Workpad" - } - } - } - } - } - }, - "elements": { - "properties": { - "total": { - "type": "long", - "_meta": { - "description": "The total number of elements across all Canvas Workpads" - } - }, - "per_page": { - "properties": { - "avg": { - "type": "float", - "_meta": { - "description": "The average number of elements per page across all Canvas Workpads" - } - }, - "min": { - "type": "long", - "_meta": { - "description": "The minimum number of elements on a page across all Canvas Workpads" - } - }, - "max": { - "type": "long", - "_meta": { - "description": "The maximum number of elements on a page across all Canvas Workpads" - } - } - } - } - } - }, - "functions": { - "properties": { - "total": { - "type": "long", - "_meta": { - "description": "The total number of functions in use across all Canvas Workpads" - } - }, - "in_use": { - "type": "array", - "items": { - "type": "keyword", - "_meta": { - "description": "A function in use in any Canvas Workpad" - } - } - }, - "in_use_30d": { - "type": "array", - "items": { - "type": "keyword", - "_meta": { - "description": "A function in use in a Canvas Workpad that has been modified in the last 30 days" - } - } - }, - "in_use_90d": { - "type": "array", - "items": { - "type": "keyword", - "_meta": { - "description": "A function in use in a Canvas Workpad that has been modified in the last 90 days" - } - } - }, - "per_element": { - "properties": { - "avg": { - "type": "float", - "_meta": { - "description": "Average number of functions used per element across all Canvas Workpads" - } - }, - "min": { - "type": "long", - "_meta": { - "description": "The minimum number of functions used in an element across all Canvas Workpads" - } - }, - "max": { - "type": "long", - "_meta": { - "description": "The maximum number of functions used in an element across all Canvas Workpads" - } - } - } - } - } - }, - "variables": { - "properties": { - "total": { - "type": "long", - "_meta": { - "description": "The total number of variables defined across all Canvas Workpads" - } - }, - "per_workpad": { - "properties": { - "avg": { - "type": "float", - "_meta": { - "description": "The average number of variables set per Canvas Workpad" - } - }, - "min": { - "type": "long", - "_meta": { - "description": "The minimum number variables set across all Canvas Workpads" - } - }, - "max": { - "type": "long", - "_meta": { - "description": "The maximum number of variables set across all Canvas Workpads" - } - } - } - } - } - }, - "custom_elements": { - "properties": { - "count": { - "type": "long", - "_meta": { - "description": "The total number of custom Canvas elements" - } - }, - "elements": { - "properties": { - "min": { - "type": "long", - "_meta": { - "description": "The minimum number of elements used across all Canvas Custom Elements" - } - }, - "max": { - "type": "long", - "_meta": { - "description": "The maximum number of elements used across all Canvas Custom Elements" - } - }, - "avg": { - "type": "float", - "_meta": { - "description": "The average number of elements used in Canvas Custom Element" - } - } - } - }, - "functions_in_use": { - "type": "array", - "items": { - "type": "keyword", - "_meta": { - "description": "The functions in use by Canvas Custom Elements" - } - } - } - } - } - } - }, "cloud": { "properties": { "isCloudEnabled": { @@ -3113,89 +2952,222 @@ } } }, - "fileUpload": { + "canvas": { "properties": { - "file_upload": { + "workpads": { "properties": { - "index_creation_count": { - "type": "long" - } - } - } - } - }, - "files": { - "properties": { - "countByExtension": { - "type": "array", - "items": { - "properties": { - "extension": { - "type": "keyword" - }, - "count": { - "type": "long" + "total": { + "type": "long", + "_meta": { + "description": "The total number of Canvas Workpads in the cluster" } } } }, - "countByStatus": { + "pages": { "properties": { - "AWAITING_UPLOAD": { + "total": { "type": "long", "_meta": { - "description": "Number of files awaiting upload" + "description": "The total number of pages across all Canvas Workpads" } }, - "DELETED": { + "per_workpad": { + "properties": { + "avg": { + "type": "float", + "_meta": { + "description": "The average number of pages across all Canvas Workpads" + } + }, + "min": { + "type": "long", + "_meta": { + "description": "The minimum number of pages found in a Canvas Workpad" + } + }, + "max": { + "type": "long", + "_meta": { + "description": "The maximum number of pages found in a Canvas Workpad" + } + } + } + } + } + }, + "elements": { + "properties": { + "total": { "type": "long", "_meta": { - "description": "Number of files that are marked as deleted" + "description": "The total number of elements across all Canvas Workpads" } }, - "READY": { + "per_page": { + "properties": { + "avg": { + "type": "float", + "_meta": { + "description": "The average number of elements per page across all Canvas Workpads" + } + }, + "min": { + "type": "long", + "_meta": { + "description": "The minimum number of elements on a page across all Canvas Workpads" + } + }, + "max": { + "type": "long", + "_meta": { + "description": "The maximum number of elements on a page across all Canvas Workpads" + } + } + } + } + } + }, + "functions": { + "properties": { + "total": { "type": "long", "_meta": { - "description": "Number of files that are ready for download" + "description": "The total number of functions in use across all Canvas Workpads" } }, - "UPLOADING": { - "type": "long", - "_meta": { - "description": "Number of files that are currently uploading" + "in_use": { + "type": "array", + "items": { + "type": "keyword", + "_meta": { + "description": "A function in use in any Canvas Workpad" + } } }, - "UPLOAD_ERROR": { + "in_use_30d": { + "type": "array", + "items": { + "type": "keyword", + "_meta": { + "description": "A function in use in a Canvas Workpad that has been modified in the last 30 days" + } + } + }, + "in_use_90d": { + "type": "array", + "items": { + "type": "keyword", + "_meta": { + "description": "A function in use in a Canvas Workpad that has been modified in the last 90 days" + } + } + }, + "per_element": { + "properties": { + "avg": { + "type": "float", + "_meta": { + "description": "Average number of functions used per element across all Canvas Workpads" + } + }, + "min": { + "type": "long", + "_meta": { + "description": "The minimum number of functions used in an element across all Canvas Workpads" + } + }, + "max": { + "type": "long", + "_meta": { + "description": "The maximum number of functions used in an element across all Canvas Workpads" + } + } + } + } + } + }, + "variables": { + "properties": { + "total": { "type": "long", "_meta": { - "description": "Number of files that failed to upload" + "description": "The total number of variables defined across all Canvas Workpads" + } + }, + "per_workpad": { + "properties": { + "avg": { + "type": "float", + "_meta": { + "description": "The average number of variables set per Canvas Workpad" + } + }, + "min": { + "type": "long", + "_meta": { + "description": "The minimum number variables set across all Canvas Workpads" + } + }, + "max": { + "type": "long", + "_meta": { + "description": "The maximum number of variables set across all Canvas Workpads" + } + } } } } }, - "storage": { + "custom_elements": { "properties": { - "esFixedSizeIndex": { + "count": { + "type": "long", + "_meta": { + "description": "The total number of custom Canvas elements" + } + }, + "elements": { "properties": { - "capacity": { + "min": { "type": "long", "_meta": { - "description": "Capacity of the fixed size index" + "description": "The minimum number of elements used across all Canvas Custom Elements" } }, - "available": { + "max": { "type": "long", "_meta": { - "description": "Available storage in bytes" + "description": "The maximum number of elements used across all Canvas Custom Elements" } }, - "used": { - "type": "long", + "avg": { + "type": "float", "_meta": { - "description": "Used storage in bytes" + "description": "The average number of elements used in Canvas Custom Element" } } } + }, + "functions_in_use": { + "type": "array", + "items": { + "type": "keyword", + "_meta": { + "description": "The functions in use by Canvas Custom Elements" + } + } + } + } + } + } + }, + "fileUpload": { + "properties": { + "file_upload": { + "properties": { + "index_creation_count": { + "type": "long" } } } @@ -3310,6 +3282,83 @@ } } }, + "files": { + "properties": { + "countByExtension": { + "type": "array", + "items": { + "properties": { + "extension": { + "type": "keyword" + }, + "count": { + "type": "long" + } + } + } + }, + "countByStatus": { + "properties": { + "AWAITING_UPLOAD": { + "type": "long", + "_meta": { + "description": "Number of files awaiting upload" + } + }, + "DELETED": { + "type": "long", + "_meta": { + "description": "Number of files that are marked as deleted" + } + }, + "READY": { + "type": "long", + "_meta": { + "description": "Number of files that are ready for download" + } + }, + "UPLOADING": { + "type": "long", + "_meta": { + "description": "Number of files that are currently uploading" + } + }, + "UPLOAD_ERROR": { + "type": "long", + "_meta": { + "description": "Number of files that failed to upload" + } + } + } + }, + "storage": { + "properties": { + "esFixedSizeIndex": { + "properties": { + "capacity": { + "type": "long", + "_meta": { + "description": "Capacity of the fixed size index" + } + }, + "available": { + "type": "long", + "_meta": { + "description": "Available storage in bytes" + } + }, + "used": { + "type": "long", + "_meta": { + "description": "Used storage in bytes" + } + } + } + } + } + } + } + }, "infraops": { "properties": { "last_24_hours": { @@ -3520,62 +3569,6 @@ } } }, - "rollups": { - "properties": { - "index_patterns": { - "properties": { - "total": { - "type": "long", - "_meta": { - "description": "Counts all the rollup index patterns" - } - } - } - }, - "saved_searches": { - "properties": { - "total": { - "type": "long", - "_meta": { - "description": "Counts all the rollup saved searches" - } - } - } - }, - "visualizations": { - "properties": { - "saved_searches": { - "properties": { - "total": { - "type": "long", - "_meta": { - "description": "Counts all the visualizations that are based on rollup saved searches" - } - }, - "lens_total": { - "type": "long", - "_meta": { - "description": "Counts all the lens visualizations that are based on rollup saved searches" - } - } - } - }, - "total": { - "type": "long", - "_meta": { - "description": "Counts all the visualizations that are based on rollup index patterns" - } - }, - "lens_total": { - "type": "long", - "_meta": { - "description": "Counts all the lens visualizations that are based on rollup index patterns" - } - } - } - } - } - }, "reporting": { "properties": { "csv_searchsource": { @@ -6330,6 +6323,62 @@ } } }, + "rollups": { + "properties": { + "index_patterns": { + "properties": { + "total": { + "type": "long", + "_meta": { + "description": "Counts all the rollup index patterns" + } + } + } + }, + "saved_searches": { + "properties": { + "total": { + "type": "long", + "_meta": { + "description": "Counts all the rollup saved searches" + } + } + } + }, + "visualizations": { + "properties": { + "saved_searches": { + "properties": { + "total": { + "type": "long", + "_meta": { + "description": "Counts all the visualizations that are based on rollup saved searches" + } + }, + "lens_total": { + "type": "long", + "_meta": { + "description": "Counts all the lens visualizations that are based on rollup saved searches" + } + } + } + }, + "total": { + "type": "long", + "_meta": { + "description": "Counts all the visualizations that are based on rollup index patterns" + } + }, + "lens_total": { + "type": "long", + "_meta": { + "description": "Counts all the lens visualizations that are based on rollup index patterns" + } + } + } + } + } + }, "saved_objects_tagging": { "properties": { "usedTags": { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_column_settings.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_column_settings.ts index 8fc60dded93d6..ff61c9d525616 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_column_settings.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_column_settings.ts @@ -34,7 +34,7 @@ type CachedColumnsPreferences = Partial<{ sortingState: EuiDataGridSorting['columns']; }>; -export interface ColumnSettings { +export interface ColumnSettingsValue { columns: EuiDataGridColumn[]; columnVisibility: { visibleColumns: string[]; @@ -45,7 +45,7 @@ export interface ColumnSettings { handleResetColumns: () => void; } -export const useColumnSettings = (): ColumnSettings => { +export const useColumnSettings = (): ColumnSettingsValue => { const { services: { storage }, } = useKibana(); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx index 900f75128b7ad..072fd42db696e 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx @@ -19,14 +19,16 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiDataGridColumn } from '@elastic/eui/src/components/datagrid/data_grid_types'; import { CellActions } from './cell_actions'; import { BrowserFields, SecuritySolutionDataViewBase } from '../../../../types'; -import { Indicator } from '../../../../../common/types/indicator'; +import { Indicator, RawIndicatorFieldId } from '../../../../../common/types/indicator'; import { cellRendererFactory } from './cell_renderer'; import { EmptyState } from '../../../../components/empty_state'; import { IndicatorsTableContext, IndicatorsTableContextValue } from './context'; import { IndicatorsFlyout } from '../indicators_flyout/indicators_flyout'; import { Pagination } from '../../hooks/use_indicators'; import { useToolbarOptions } from './hooks/use_toolbar_options'; -import { ColumnSettings } from './hooks/use_column_settings'; +import { ColumnSettingsValue } from './hooks/use_column_settings'; +import { useFieldTypes } from '../../../../hooks/use_field_types'; +import { getFieldSchema } from '../../lib/get_field_schema'; export interface IndicatorsTableProps { indicators: Indicator[]; @@ -37,7 +39,7 @@ export interface IndicatorsTableProps { loading: boolean; indexPattern: SecuritySolutionDataViewBase; browserFields: BrowserFields; - columnSettings: ColumnSettings; + columnSettings: ColumnSettingsValue; } export const TABLE_TEST_ID = 'tiIndicatorsTable'; @@ -61,6 +63,8 @@ export const IndicatorsTable: VFC = ({ }) => { const [expanded, setExpanded] = useState(); + const fieldTypes = useFieldTypes(); + const renderCellValue = useMemo( () => cellRendererFactory(pagination.pageIndex * pagination.pageSize), [pagination.pageIndex, pagination.pageSize] @@ -91,22 +95,28 @@ export const IndicatorsTable: VFC = ({ [renderCellValue] ); - useMemo(() => { - columns.forEach( - (col: EuiDataGridColumn) => - (col.cellActions = [ - ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => ( - - ), - ]) - ); - }, [columns, indicators, pagination]); + const mappedColumns = useMemo( + () => + columns.map((col: EuiDataGridColumn) => { + return { + ...col, + isSortable: col.id !== RawIndicatorFieldId.Id && browserFields[col.id]?.aggregatable, + schema: getFieldSchema(fieldTypes[col.id]), + cellActions: [ + ({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => ( + + ), + ], + }; + }), + [browserFields, columns, fieldTypes, indicators, pagination] + ); const toolbarOptions = useToolbarOptions({ browserFields, @@ -159,12 +169,12 @@ export const IndicatorsTable: VFC = ({ data-test-subj={TABLE_TEST_ID} sorting={sorting} columnVisibility={columnVisibility} - columns={columns} + columns={mappedColumns} /> ); }, [ columnVisibility, - columns, + mappedColumns, indicatorCount, leadingControlColumns, loading, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_field_schema.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_field_schema.ts new file mode 100644 index 0000000000000..466c992cdead9 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/lib/get_field_schema.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { KBN_FIELD_TYPES } from '@kbn/data-plugin/public'; + +/** + * Returns datagrid field schema for field type, the same implementation as in discover + */ +export const getFieldSchema = (kbnType: string | undefined) => { + // Default DataGrid schemas: boolean, numeric, datetime, json, currency, string + switch (kbnType) { + case KBN_FIELD_TYPES.IP: + case KBN_FIELD_TYPES.GEO_SHAPE: + case KBN_FIELD_TYPES.NUMBER: + return 'numeric'; + case KBN_FIELD_TYPES.BOOLEAN: + return 'boolean'; + case KBN_FIELD_TYPES.STRING: + return 'string'; + case KBN_FIELD_TYPES.DATE: + return 'datetime'; + default: + return undefined; + } +}; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 516824f72afef..5cc459505956d 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -972,10 +972,8 @@ "dashboard.listing.createNewDashboard.title": "Créer votre premier tableau de bord", "dashboard.listing.readonlyNoItemsBody": "Aucun tableau de bord n'est disponible. Pour modifier vos autorisations afin d’afficher les tableaux de bord dans cet espace, contactez votre administrateur.", "dashboard.listing.readonlyNoItemsTitle": "Aucun tableau de bord à afficher", - "dashboard.listing.table.descriptionColumnName": "Description", "dashboard.listing.table.entityName": "tableau de bord", "dashboard.listing.table.entityNamePlural": "tableaux de bord", - "dashboard.listing.table.titleColumnName": "Titre", "dashboard.listing.unsaved.discardTitle": "Abandonner les modifications", "dashboard.listing.unsaved.editTitle": "Poursuivre les modifications", "dashboard.listing.unsaved.loading": "Chargement", @@ -2329,7 +2327,6 @@ "discover.viewModes.document.label": "Documents", "discover.viewModes.fieldStatistics.betaTitle": "Bêta", "discover.viewModes.fieldStatistics.label": "Statistiques de champ", - "discover.viewModes.legend": "Modes d'affichage", "embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} a été ajouté.", "embeddableApi.attributeService.saveToLibraryError": "Une erreur s'est produite lors de l'enregistrement. Erreur : {errorMessage}.", "embeddableApi.errors.embeddableFactoryNotFound": "Impossible de charger {type}. Veuillez effectuer une mise à niveau vers la distribution par défaut d'Elasticsearch et de Kibana avec la licence appropriée.", @@ -4308,15 +4305,6 @@ "kibana-react.noDataPage.intro": "Ajoutez vos données pour commencer, ou {link} sur {solution}.", "kibana-react.noDataPage.welcomeTitle": "Bienvenue dans Elastic {solution}.", "kibana-react.solutionNav.mobileTitleText": "Menu {solutionName}", - "kibana-react.tableListView.listing.createNewItemButtonLabel": "Créer {entityName}", - "kibana-react.tableListView.listing.deleteButtonMessage": "Supprimer {itemCount} {entityName}", - "kibana-react.tableListView.listing.deleteConfirmModalDescription": "Vous ne pourrez pas récupérer les {entityNamePlural} supprimés.", - "kibana-react.tableListView.listing.deleteSelectedConfirmModal.title": "Supprimer {itemCount} {entityName} ?", - "kibana-react.tableListView.listing.fetchErrorDescription": "Le listing {entityName} n'a pas pu être récupéré : {message}.", - "kibana-react.tableListView.listing.listingLimitExceededDescription": "Vous avez {totalItems} {entityNamePlural}, mais votre paramètre {listingLimitText} empêche le tableau ci-dessous d'en afficher plus de {listingLimitValue}. Vous pouvez modifier ce paramètre sous {advancedSettingsLink}.", - "kibana-react.tableListView.listing.listingLimitExceededDescriptionNoPermissions": "Vous avez {totalItems} {entityNamePlural}, mais votre paramètre {listingLimitText} empêche le tableau ci-dessous d'en afficher plus de {listingLimitValue}. Contactez l'administrateur système pour modifier ce paramètre.", - "kibana-react.tableListView.listing.table.editActionName": "Modifier {itemDescription}", - "kibana-react.tableListView.listing.unableToDeleteDangerMessage": "Impossible de supprimer la/le/les {entityName}(s)", "kibana-react.dualRangeControl.maxInputAriaLabel": "Maximum de la plage", "kibana-react.dualRangeControl.minInputAriaLabel": "Minimum de la plage", "kibana-react.dualRangeControl.mustSetBothErrorMessage": "Les valeurs inférieure et supérieure doivent être définies.", @@ -4344,16 +4332,6 @@ "kibana-react.pageFooter.makeDefaultRouteLink": "Choisir comme page de destination", "kibana-react.solutionNav.collapsibleLabel": "Réduire la navigation latérale", "kibana-react.solutionNav.openLabel": "Ouvrir la navigation latérale", - "kibana-react.tableListView.lastUpdatedColumnTitle": "Dernière mise à jour", - "kibana-react.tableListView.listing.deleteSelectedItemsConfirmModal.cancelButtonLabel": "Annuler", - "kibana-react.tableListView.listing.deleteSelectedItemsConfirmModal.confirmButtonLabel": "Supprimer", - "kibana-react.tableListView.listing.deleteSelectedItemsConfirmModal.confirmButtonLabelDeleting": "Suppression", - "kibana-react.tableListView.listing.fetchErrorTitle": "Échec de la récupération du listing", - "kibana-react.tableListView.listing.listingLimitExceeded.advancedSettingsLinkText": "Paramètres avancés", - "kibana-react.tableListView.listing.listingLimitExceededTitle": "Limite de listing dépassée", - "kibana-react.tableListView.listing.table.actionTitle": "Actions", - "kibana-react.tableListView.listing.table.editActionDescription": "Modifier", - "kibana-react.tableListView.updatedDateUnknownLabel": "Dernière mise à jour inconnue", "management.landing.header": "Bienvenue dans Gestion de la Suite {version}", "management.breadcrumb": "Gestion de la Suite", "management.landing.subhead": "Gérez vos index, vues de données, objets enregistrés, paramètres Kibana et plus encore.", @@ -6235,11 +6213,9 @@ "visualizations.listing.createNew.title": "Créer votre première visualisation", "visualizations.listing.experimentalTitle": "Version d'évaluation technique", "visualizations.listing.experimentalTooltip": "Cette fonctionnalité est en version d'évaluation technique et pourra être modifiée ou retirée complètement dans une future version. Elastic s'efforcera au maximum de corriger tout problème, mais les fonctionnalités en version d'évaluation technique ne sont pas soumises aux accords de niveau de service d'assistance des fonctionnalités officielles en disponibilité générale.", - "visualizations.listing.table.descriptionColumnName": "Description", "visualizations.listing.table.entityName": "visualisation", "visualizations.listing.table.entityNamePlural": "visualisations", "visualizations.listing.table.listTitle": "Bibliothèque Visualize", - "visualizations.listing.table.titleColumnName": "Titre", "visualizations.listing.table.typeColumnName": "Type", "visualizations.listingPageTitle": "Bibliothèque Visualize", "visualizations.missedDataView.dataViewReconfigure": "Recréez-la dans la page de gestion des vues de données.", @@ -6304,35 +6280,6 @@ "xpack.actions.actionTypeRegistry.register.invalidConnectorFeatureIds": "ID \"{ids}\" de fonctionnalité non valides pour le type de connecteur \"{connectorTypeId}\".", "xpack.actions.actionTypeRegistry.register.missingSupportedFeatureIds": "Au moins une valeur \"supportedFeatureId\" doit être fournie pour le type de connecteur \"{connectorTypeId}\".", "xpack.actions.apiAllowedHostsError": "erreur lors de la configuration de l'action du connecteur : {message}", - "xpack.actions.builtin.casesWebhook.casesWebhookConfigurationError": "erreur lors de la configuration de l'action webhook des cas : {err}", - "xpack.actions.builtin.casesWebhook.casesWebhookConfigurationErrorNoHostname": "erreur lors de la configuration de l'action webhook des cas : impossible d'analyser l'url {url} : {err}", - "xpack.actions.builtin.casesWebhook.configuration.apiAllowedHostsError": "erreur lors de la configuration de l'action du connecteur : {message}", - "xpack.actions.builtin.configuration.apiAllowedHostsError": "erreur lors de la configuration de l'action du connecteur : {message}", - "xpack.actions.builtin.configuration.apiValidateMissingOAuthFieldError": "{field} doit être fourni quand isOAuth = {isOAuth}", - "xpack.actions.builtin.configuration.apiValidateOAuthFieldError": "{field} ne doit pas être fourni quand isOAuth = {isOAuth}", - "xpack.actions.builtin.email.customViewInKibanaMessage": "Ce message a été envoyé par Kibana. [{kibanaFooterLinkText}]({link}).", - "xpack.actions.builtin.jira.configuration.apiAllowedHostsError": "erreur lors de la configuration de l'action du connecteur : {message}", - "xpack.actions.builtin.pagerduty.invalidTimestampErrorMessage": "erreur lors de l'analyse de l'horodatage \"{timestamp}\"", - "xpack.actions.builtin.pagerduty.missingDedupkeyErrorMessage": "DedupKey est requis lorsque eventAction est \"{eventAction}\"", - "xpack.actions.builtin.pagerduty.pagerdutyConfigurationError": "erreur lors de la configuration de l'action pagerduty : {message}", - "xpack.actions.builtin.pagerduty.postingRetryErrorMessage": "erreur lors de la publication de l'événement pagerduty : statut http {status}, réessayer ultérieurement", - "xpack.actions.builtin.pagerduty.postingUnexpectedErrorMessage": "erreur lors de la publication de l'événement pagerduty : statut inattendu {status}", - "xpack.actions.builtin.pagerduty.timestampParsingFailedErrorMessage": "erreur lors de l'analyse de l'horodatage \"{timestamp}\" : {message}", - "xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage": "erreur lors de la publication d'un message slack, réessayer à cette date/heure : {retryString}", - "xpack.actions.builtin.slack.slackConfigurationError": "erreur lors de la configuration de l'action slack : {message}", - "xpack.actions.builtin.slack.unexpectedHttpResponseErrorMessage": "réponse http inattendue de Slack : {httpStatus} {httpStatusText}", - "xpack.actions.builtin.swimlane.configuration.apiAllowedHostsError": "erreur lors de la configuration de l'action du connecteur : {message}", - "xpack.actions.builtin.teams.errorPostingRetryDateErrorMessage": "erreur lors de la publication d'un message Microsoft Teams, réessayer à cette date/heure : {retryString}", - "xpack.actions.builtin.teams.teamsConfigurationError": "erreur lors de la configuration de l'action teams : {message}", - "xpack.actions.builtin.webhook.invalidResponseRetryDateErrorMessage": "erreur lors de l'appel de webhook, réessayer à cette date/heure : {retryString}", - "xpack.actions.builtin.webhook.webhookConfigurationError": "erreur lors de la configuration de l'action webhook : {message}", - "xpack.actions.builtin.webhook.webhookConfigurationErrorNoHostname": "erreur lors de la configuration de l'action webhook : impossible d'analyser l'url : {err}", - "xpack.actions.builtin.xmatters.postingRetryErrorMessage": "Erreur lors du déclenchement du flux xMatters : statut http {status}, réessayer plus tard", - "xpack.actions.builtin.xmatters.unexpectedStatusErrorMessage": "Erreur de déclenchement du flux xMatters : statut inattendu {status}", - "xpack.actions.builtin.xmatters.xmattersConfigurationError": "Erreur lors de la configuration de l'action xMatters : {message}", - "xpack.actions.builtin.xmatters.xmattersConfigurationErrorNoHostname": "Erreur lors de la configuration de l'action xMatters : impossible d'analyser l'url : {err}", - "xpack.actions.builtin.xmatters.xmattersHostnameNotAllowed": "{message}", - "xpack.actions.builtin.xmatters.xmattersInvalidUrlError": "secretsUrl non valide : {err}", "xpack.actions.disabledActionTypeError": "le type d'action \"{actionType}\" n'est pas activé dans la configuration Kibana xpack.actions.enabledActionTypes", "xpack.actions.savedObjects.onImportText": "{connectorsWithSecretsLength} {connectorsWithSecretsLength, plural, one {Le connecteur contient} other {Les connecteurs contiennent}} des informations sensibles qui requièrent des mises à jour.", "xpack.actions.serverSideErrors.expirerdLicenseErrorMessage": "Le type d'action {actionTypeId} est désactivé, car votre licence {licenseType} a expiré.", @@ -6347,60 +6294,89 @@ "xpack.actions.availableConnectorFeatures.cases": "Cas", "xpack.actions.availableConnectorFeatures.securitySolution": "Solution de sécurité", "xpack.actions.availableConnectorFeatures.uptime": "Uptime", - "xpack.actions.builtin.case.swimlaneTitle": "Swimlane", - "xpack.actions.builtin.cases.casesWebhookTitle": "Webhook - Gestion des cas", - "xpack.actions.builtin.cases.jiraTitle": "Jira", - "xpack.actions.builtin.cases.resilientTitle": "IBM Resilient", - "xpack.actions.builtin.casesWebhook.invalidUsernamePassword": "l'utilisateur et le mot de passe doivent être spécifiés", - "xpack.actions.builtin.configuration.apiBasicAuthCredentialsError": "le nom d'utilisateur et le mot de passe doivent être tous deux spécifiés", - "xpack.actions.builtin.configuration.apiCredentialsError": "Les informations d'identification auth ou OAuth de base doivent être spécifiées", - "xpack.actions.builtin.configuration.apiOAuthCredentialsError": "clientSecret et privateKey doivent tous deux être spécifiés", - "xpack.actions.builtin.email.errorSendingErrorMessage": "erreur lors de l'envoi de l'e-mail", - "xpack.actions.builtin.email.kibanaFooterLinkText": "Accéder à Kibana", - "xpack.actions.builtin.email.sentByKibanaMessage": "Ce message a été envoyé par Kibana.", - "xpack.actions.builtin.emailTitle": "E-mail", - "xpack.actions.builtin.esIndex.errorIndexingErrorMessage": "erreur lors de l'indexation des documents", - "xpack.actions.builtin.esIndexTitle": "Index", - "xpack.actions.builtin.pagerduty.postingErrorMessage": "erreur lors de la publication de l'événement pagerduty", - "xpack.actions.builtin.pagerdutyTitle": "PagerDuty", - "xpack.actions.builtin.serverLog.errorLoggingErrorMessage": "erreur lors du logging du message", - "xpack.actions.builtin.serverLogTitle": "Log de serveur", - "xpack.actions.builtin.serviceNowITOMTitle": "ServiceNow ITOM", - "xpack.actions.builtin.serviceNowITSMTitle": "ServiceNow ITSM", - "xpack.actions.builtin.serviceNowSIRTitle": "ServiceNow SecOps", - "xpack.actions.builtin.serviceNowTitle": "ServiceNow", - "xpack.actions.builtin.slack.errorPostingErrorMessage": "erreur lors de la publication du message slack", - "xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage": "erreur lors de la publication d'un message slack, réessayer ultérieurement", - "xpack.actions.builtin.slack.slackConfigurationErrorNoHostname": "erreur lors de la configuration de l'action slack : impossible d'analyser le nom de l'hôte depuis webhookUrl", - "xpack.actions.builtin.slack.unexpectedNullResponseErrorMessage": "réponse nulle inattendue de Slack", - "xpack.actions.builtin.slackTitle": "Slack", - "xpack.actions.builtin.swimlaneTitle": "Swimlane", - "xpack.actions.builtin.teams.errorPostingRetryLaterErrorMessage": "erreur lors de la publication d'un message Microsoft Teams, réessayer ultérieurement", - "xpack.actions.builtin.teams.invalidResponseErrorMessage": "erreur lors de la publication sur Microsoft Teams, réponse non valide", - "xpack.actions.builtin.teams.teamsConfigurationErrorNoHostname": "erreur lors de la configuration de l'action teams : impossible d'analyser le nom de l'hôte depuis webhookUrl", - "xpack.actions.builtin.teams.unreachableErrorMessage": "erreur lors de la publication sur Microsoft Teams, erreur inattendue", - "xpack.actions.builtin.teamsTitle": "Microsoft Teams", - "xpack.actions.builtin.webhook.invalidResponseErrorMessage": "erreur lors de l'appel de webhook, réponse non valide", - "xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage": "erreur lors de l'appel de webhook, réessayer ultérieurement", - "xpack.actions.builtin.webhook.invalidUsernamePassword": "l'utilisateur et le mot de passe doivent être spécifiés", - "xpack.actions.builtin.webhook.requestFailedErrorMessage": "erreur lors de l'appel de webhook, requête échouée", - "xpack.actions.builtin.webhook.unreachableErrorMessage": "erreur lors de l'appel de webhook, erreur inattendue", - "xpack.actions.builtin.webhookTitle": "Webhook", - "xpack.actions.builtin.xmatters.invalidUsernamePassword": "L'utilisateur et le mot de passe doivent être spécifiés.", - "xpack.actions.builtin.xmatters.missingConfigUrl": "Fournir une configUrl valide", - "xpack.actions.builtin.xmatters.missingPassword": "Fournir un mot de passe valide", - "xpack.actions.builtin.xmatters.missingSecretsUrl": "Fournir une secretsUrl valide avec la clé d'API", - "xpack.actions.builtin.xmatters.missingUser": "Fournir un nom d'utilisateur valide", - "xpack.actions.builtin.xmatters.noSecretsProvided": "Fournir le lien secretsUrl ou le nom d'utilisateur/le mot de passe pour vous authentifier", - "xpack.actions.builtin.xmatters.noUserPassWhenSecretsUrl": "Impossible d'utiliser le nom d'utilisateur/le mot de passe pour l'authentification de l'URL. Fournir une secretsUrl valide ou utiliser l'authentification de base.", - "xpack.actions.builtin.xmatters.postingErrorMessage": "Erreur de déclenchement du workflow xMatters", - "xpack.actions.builtin.xmatters.shouldNotHaveConfigUrl": "configUrl ne doit pas être fournie lorsque usesBasic est faux", - "xpack.actions.builtin.xmatters.shouldNotHaveSecretsUrl": "secretsUrl ne doit pas être fournie lorsque usesBasic est vrai", - "xpack.actions.builtin.xmatters.shouldNotHaveUsernamePassword": "Le nom d'utilisateur et le mot de passe ne doivent pas être fournis lorsque usesBasic est faux", - "xpack.actions.builtin.xmattersTitle": "xMatters", "xpack.actions.featureRegistry.actionsFeatureName": "Actions et connecteurs", "xpack.actions.savedObjects.goToConnectorsButtonText": "Accéder aux connecteurs", "xpack.actions.serverSideErrors.unavailableLicenseInformationErrorMessage": "Les actions sont indisponibles - les informations de licence ne sont pas disponibles actuellement.", + "xpack.stackConnectors.casesWebhook.configurationError": "erreur lors de la configuration de l'action webhook des cas : {err}", + "xpack.stackConnectors.casesWebhook.configurationErrorNoHostname": "erreur lors de la configuration de l'action webhook des cas : impossible d'analyser l'url {url} : {err}", + "xpack.stackConnectors.casesWebhook.configuration.apiAllowedHostsError": "erreur lors de la configuration de l'action du connecteur : {message}", + "xpack.stackConnectors.resilient.configuration.apiAllowedHostsError": "erreur lors de la configuration de l'action du connecteur : {message}", + "xpack.stackConnectors.serviceNow.configuration.apiAllowedHostsError": "erreur lors de la configuration de l'action du connecteur : {message}", + "xpack.stackConnectors.serviceNow.configuration.apiValidateMissingOAuthFieldError": "{field} doit être fourni quand isOAuth = {isOAuth}", + "xpack.stackConnectors.serviceNow.configuration.apiValidateOAuthFieldError": "{field} ne doit pas être fourni quand isOAuth = {isOAuth}", + "xpack.stackConnectors.email.customViewInKibanaMessage": "Ce message a été envoyé par Kibana. [{kibanaFooterLinkText}]({link}).", + "xpack.stackConnectors.jira.configuration.apiAllowedHostsError": "erreur lors de la configuration de l'action du connecteur : {message}", + "xpack.stackConnectors.pagerduty.invalidTimestampErrorMessage": "erreur lors de l'analyse de l'horodatage \"{timestamp}\"", + "xpack.stackConnectors.pagerduty.missingDedupkeyErrorMessage": "DedupKey est requis lorsque eventAction est \"{eventAction}\"", + "xpack.stackConnectors.pagerduty.configurationError": "erreur lors de la configuration de l'action pagerduty : {message}", + "xpack.stackConnectors.pagerduty.postingRetryErrorMessage": "erreur lors de la publication de l'événement pagerduty : statut http {status}, réessayer ultérieurement", + "xpack.stackConnectors.pagerduty.postingUnexpectedErrorMessage": "erreur lors de la publication de l'événement pagerduty : statut inattendu {status}", + "xpack.stackConnectors.pagerduty.timestampParsingFailedErrorMessage": "erreur lors de l'analyse de l'horodatage \"{timestamp}\" : {message}", + "xpack.stackConnectors.slack.errorPostingRetryDateErrorMessage": "erreur lors de la publication d'un message slack, réessayer à cette date/heure : {retryString}", + "xpack.stackConnectors.slack.configurationError": "erreur lors de la configuration de l'action slack : {message}", + "xpack.stackConnectors.slack.unexpectedHttpResponseErrorMessage": "réponse http inattendue de Slack : {httpStatus} {httpStatusText}", + "xpack.stackConnectors.swimlane.configuration.apiAllowedHostsError": "erreur lors de la configuration de l'action du connecteur : {message}", + "xpack.stackConnectors.teams.errorPostingRetryDateErrorMessage": "erreur lors de la publication d'un message Microsoft Teams, réessayer à cette date/heure : {retryString}", + "xpack.stackConnectors.teams.configurationError": "erreur lors de la configuration de l'action teams : {message}", + "xpack.stackConnectors.webhook.invalidResponseRetryDateErrorMessage": "erreur lors de l'appel de webhook, réessayer à cette date/heure : {retryString}", + "xpack.stackConnectors.webhook.configurationError": "erreur lors de la configuration de l'action webhook : {message}", + "xpack.stackConnectors.webhook.configurationErrorNoHostname": "erreur lors de la configuration de l'action webhook : impossible d'analyser l'url : {err}", + "xpack.stackConnectors.xmatters.postingRetryErrorMessage": "Erreur lors du déclenchement du flux xMatters : statut http {status}, réessayer plus tard", + "xpack.stackConnectors.xmatters.unexpectedStatusErrorMessage": "Erreur de déclenchement du flux xMatters : statut inattendu {status}", + "xpack.stackConnectors.xmatters.configurationError": "Erreur lors de la configuration de l'action xMatters : {message}", + "xpack.stackConnectors.xmatters.configurationErrorNoHostname": "Erreur lors de la configuration de l'action xMatters : impossible d'analyser l'url : {err}", + "xpack.stackConnectors.xmatters.hostnameNotAllowed": "{message}", + "xpack.stackConnectors.xmatters.invalidUrlError": "secretsUrl non valide : {err}", + "xpack.stackConnectors.casesWebhook.title": "Webhook - Gestion des cas", + "xpack.stackConnectors.jira.title": "Jira", + "xpack.stackConnectors.resilient.title": "IBM Resilient", + "xpack.stackConnectors.casesWebhook.invalidUsernamePassword": "l'utilisateur et le mot de passe doivent être spécifiés", + "xpack.stackConnectors.serviceNow.configuration.apiBasicAuthCredentialsError": "le nom d'utilisateur et le mot de passe doivent être tous deux spécifiés", + "xpack.stackConnectors.serviceNow.configuration.apiCredentialsError": "Les informations d'identification auth ou OAuth de base doivent être spécifiées", + "xpack.stackConnectors.serviceNow.configuration.apiOAuthCredentialsError": "clientSecret et privateKey doivent tous deux être spécifiés", + "xpack.stackConnectors.email.errorSendingErrorMessage": "erreur lors de l'envoi de l'e-mail", + "xpack.stackConnectors.email.kibanaFooterLinkText": "Accéder à Kibana", + "xpack.stackConnectors.email.sentByKibanaMessage": "Ce message a été envoyé par Kibana.", + "xpack.stackConnectors.email.title": "E-mail", + "xpack.stackConnectors.esIndex.errorIndexingErrorMessage": "erreur lors de l'indexation des documents", + "xpack.stackConnectors.esIndex.title": "Index", + "xpack.stackConnectors.pagerduty.postingErrorMessage": "erreur lors de la publication de l'événement pagerduty", + "xpack.stackConnectors.pagerduty.title": "PagerDuty", + "xpack.stackConnectors.serverLog.errorLoggingErrorMessage": "erreur lors du logging du message", + "xpack.stackConnectors.serverLog.title": "Log de serveur", + "xpack.stackConnectors.serviceNowITOM.title": "ServiceNow ITOM", + "xpack.stackConnectors.serviceNowITSM.title": "ServiceNow ITSM", + "xpack.stackConnectors.serviceNowSIR.title": "ServiceNow SecOps", + "xpack.stackConnectors.serviceNow.title": "ServiceNow", + "xpack.stackConnectors.slack.errorPostingErrorMessage": "erreur lors de la publication du message slack", + "xpack.stackConnectors.slack.errorPostingRetryLaterErrorMessage": "erreur lors de la publication d'un message slack, réessayer ultérieurement", + "xpack.stackConnectors.slack.configurationErrorNoHostname": "erreur lors de la configuration de l'action slack : impossible d'analyser le nom de l'hôte depuis webhookUrl", + "xpack.stackConnectors.slack.unexpectedNullResponseErrorMessage": "réponse nulle inattendue de Slack", + "xpack.stackConnectors.slack.title": "Slack", + "xpack.stackConnectors.swimlane.title": "Swimlane", + "xpack.stackConnectors.teams.errorPostingRetryLaterErrorMessage": "erreur lors de la publication d'un message Microsoft Teams, réessayer ultérieurement", + "xpack.stackConnectors.teams.invalidResponseErrorMessage": "erreur lors de la publication sur Microsoft Teams, réponse non valide", + "xpack.stackConnectors.teams.configurationErrorNoHostname": "erreur lors de la configuration de l'action teams : impossible d'analyser le nom de l'hôte depuis webhookUrl", + "xpack.stackConnectors.teams.unreachableErrorMessage": "erreur lors de la publication sur Microsoft Teams, erreur inattendue", + "xpack.stackConnectors.teams.title": "Microsoft Teams", + "xpack.stackConnectors.webhook.invalidResponseErrorMessage": "erreur lors de l'appel de webhook, réponse non valide", + "xpack.stackConnectors.webhook.invalidResponseRetryLaterErrorMessage": "erreur lors de l'appel de webhook, réessayer ultérieurement", + "xpack.stackConnectors.webhook.invalidUsernamePassword": "l'utilisateur et le mot de passe doivent être spécifiés", + "xpack.stackConnectors.webhook.requestFailedErrorMessage": "erreur lors de l'appel de webhook, requête échouée", + "xpack.stackConnectors.webhook.unreachableErrorMessage": "erreur lors de l'appel de webhook, erreur inattendue", + "xpack.stackConnectors.webhook.title": "Webhook", + "xpack.stackConnectors.xmatters.invalidUsernamePassword": "L'utilisateur et le mot de passe doivent être spécifiés.", + "xpack.stackConnectors.xmatters.missingConfigUrl": "Fournir une configUrl valide", + "xpack.stackConnectors.xmatters.missingPassword": "Fournir un mot de passe valide", + "xpack.stackConnectors.xmatters.missingSecretsUrl": "Fournir une secretsUrl valide avec la clé d'API", + "xpack.stackConnectors.xmatters.missingUser": "Fournir un nom d'utilisateur valide", + "xpack.stackConnectors.xmatters.noSecretsProvided": "Fournir le lien secretsUrl ou le nom d'utilisateur/le mot de passe pour vous authentifier", + "xpack.stackConnectors.xmatters.noUserPassWhenSecretsUrl": "Impossible d'utiliser le nom d'utilisateur/le mot de passe pour l'authentification de l'URL. Fournir une secretsUrl valide ou utiliser l'authentification de base.", + "xpack.stackConnectors.xmatters.postingErrorMessage": "Erreur de déclenchement du workflow xMatters", + "xpack.stackConnectors.xmatters.shouldNotHaveConfigUrl": "configUrl ne doit pas être fournie lorsque usesBasic est faux", + "xpack.stackConnectors.xmatters.shouldNotHaveSecretsUrl": "secretsUrl ne doit pas être fournie lorsque usesBasic est vrai", + "xpack.stackConnectors.xmatters.shouldNotHaveUsernamePassword": "Le nom d'utilisateur et le mot de passe ne doivent pas être fournis lorsque usesBasic est faux", + "xpack.stackConnectors.xmatters.title": "xMatters", "xpack.aiops.explainLogRateSpikes.loadingState.identifiedFieldCandidates": "{fieldCandidatesCount, plural, one {# candidat de champ identifié} other {# candidats de champs identifiés}}.", "xpack.aiops.explainLogRateSpikes.loadingState.identifiedFieldValuePairs": "{fieldValuePairsCount, plural, one {# paire significative champ/valeur identifiée} other {# paires significatives champ/valeur identifiées}}.", "xpack.aiops.index.dataLoader.internalServerErrorMessage": "Erreur lors du chargement des données dans l'index {index}. {message}. La requête a peut-être expiré. Essayez d'utiliser un échantillon d'une taille inférieure ou de réduire la plage temporelle.", @@ -7221,12 +7197,7 @@ "xpack.apm.serviceIcons.serviceDetails.cloud.projectIdLabel": "ID de projet", "xpack.apm.serviceIcons.serviceDetails.cloud.providerLabel": "Fournisseur cloud", "xpack.apm.serviceIcons.serviceDetails.cloud.serviceNameLabel": "Service Cloud", - "xpack.apm.serviceIcons.serviceDetails.container.containerizedLabel": "Conteneurisé", - "xpack.apm.serviceIcons.serviceDetails.container.noLabel": "Non", - "xpack.apm.serviceIcons.serviceDetails.container.orchestrationLabel": "Orchestration", - "xpack.apm.serviceIcons.serviceDetails.container.osLabel": "Système d'exploitation", "xpack.apm.serviceIcons.serviceDetails.container.totalNumberInstancesLabel": "Nombre total d'instances", - "xpack.apm.serviceIcons.serviceDetails.container.yesLabel": "Oui", "xpack.apm.serviceIcons.serviceDetails.service.agentLabel": "Nom et version de l'agent", "xpack.apm.serviceIcons.serviceDetails.service.frameworkLabel": "Nom du framework", "xpack.apm.serviceIcons.serviceDetails.service.runtimeLabel": "Nom et version de l'exécution", @@ -9592,7 +9563,6 @@ "xpack.csp.cloudPosturePage.errorRenderer.errorTitle": "Nous n'avons pas pu récupérer vos données sur le niveau de sécurité du cloud.", "xpack.csp.cloudPosturePage.loadingDescription": "Chargement...", "xpack.csp.cloudPosturePage.packageNotInstalled.buttonLabel": "Ajouter une intégration CIS", - "xpack.csp.cloudPosturePage.packageNotInstalled.description": "Utilisez notre intégration CIS Kubernetes Benchmark pour mesurer votre configuration de cluster Kubernetes par rapport aux recommandations du CIS.", "xpack.csp.cloudPosturePage.packageNotInstalled.pageTitle": "Installer l'intégration pour commencer", "xpack.csp.cloudPosturePage.packageNotInstalled.solutionNameLabel": "Niveau de sécurité du cloud", "xpack.csp.cspEvaluationBadge.failLabel": "Échec", @@ -13625,10 +13595,8 @@ "xpack.graph.listing.graphsTitle": "Graphes", "xpack.graph.listing.noDataSource.sampleDataInstallLinkText": "exemple de données", "xpack.graph.listing.noItemsMessage": "Il semble que vous n'avez pas de graphe.", - "xpack.graph.listing.table.descriptionColumnName": "Description", "xpack.graph.listing.table.entityName": "graphe", "xpack.graph.listing.table.entityNamePlural": "graphes", - "xpack.graph.listing.table.titleColumnName": "Titre", "xpack.graph.missingWorkspaceErrorMessage": "Impossible de charger le graphe avec l'ID", "xpack.graph.newGraphTitle": "Graphe non enregistré", "xpack.graph.noDataSourceNotificationMessageText.managementDataViewLinkText": "Gestion > Vues de données", @@ -17823,7 +17791,6 @@ "xpack.lens.xyChart.addLayerTooltip": "Utilisez plusieurs calques pour combiner les types de visualisation ou pour visualiser différentes vues de données.", "xpack.lens.xyChart.addReferenceLineLayerLabel": "Lignes de référence", "xpack.lens.xyChart.addReferenceLineLayerLabelDisabledHelp": "Ajouter des données pour activer le calque de référence", - "xpack.lens.xyChart.annotation.name": "Masquer l’annotation", "xpack.lens.xyChart.annotationDate": "Date de l’annotation", "xpack.lens.xyChart.annotationDate.from": "De", "xpack.lens.xyChart.annotationDate.to": "À", @@ -18457,12 +18424,9 @@ "xpack.maps.map.initializeErrorTitle": "Initialisation de la carte impossible", "xpack.maps.mapActions.addFeatureError": "Impossible d’ajouter la fonctionnalité à l’index.", "xpack.maps.mapActions.removeFeatureError": "Impossible de retirer la fonctionnalité de l’index.", - "xpack.maps.mapListing.descriptionFieldTitle": "Description", "xpack.maps.mapListing.entityName": "carte", "xpack.maps.mapListing.entityNamePlural": "cartes", "xpack.maps.mapListing.errorAttemptingToLoadSavedMaps": "Impossible de charger les cartes", - "xpack.maps.mapListing.tableCaption": "Cartes", - "xpack.maps.mapListing.titleFieldTitle": "Titre", "xpack.maps.mapSavedObjectLabel": "Carte", "xpack.maps.mapSettingsPanel.addCustomIcon": "Ajouter une icône personnalisée", "xpack.maps.mapSettingsPanel.autoFitToBoundsLocationLabel": "Ajuster automatiquement la carte aux limites de données", @@ -25576,11 +25540,9 @@ "xpack.securitySolution.administration.os.windows": "Windows", "xpack.securitySolution.alertDetails.enrichmentQueryEndDate": "Date de fin", "xpack.securitySolution.alertDetails.enrichmentQueryStartDate": "Date de début", - "xpack.securitySolution.alertDetails.hostRiskClassification": "Classification de risque de l'hôte", "xpack.securitySolution.alertDetails.investigationTimeQueryTitle": "Enrichissement avec la Threat Intelligence", "xpack.securitySolution.alertDetails.noEnrichmentsFoundDescription": "Nous n'avons pas trouvé de Threat Intelligence correspondant à l'une de vos règles de correspondance d'indicateur ou à un enrichissement pour cette alerte.", "xpack.securitySolution.alertDetails.noInvestigationEnrichmentsDescription": "Nous n'avons pas trouvé de valeur de champ comportant des informations supplémentaires disponibles depuis les sources de Threat Intelligence dans lesquelles nous avons lancé la recherche sur les 30 derniers jours par défaut.", - "xpack.securitySolution.alertDetails.noRiskDataDescription": "Aucune donnée de risque de l’hôte n’a été détectée pour cette alerte.", "xpack.securitySolution.alertDetails.overview": "Aperçu", "xpack.securitySolution.alertDetails.overview.enrichedDataTitle": "Données enrichies", "xpack.securitySolution.alertDetails.overview.highlightedFields": "Champs en surbrillance", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 7c9157abe6c54..04231fcb4bf97 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -970,10 +970,8 @@ "dashboard.listing.createNewDashboard.title": "初めてのダッシュボードを作成してみましょう。", "dashboard.listing.readonlyNoItemsBody": "使用可能なダッシュボードはありません。権限を変更してこのスペースにダッシュボードを表示するには、管理者に問い合わせてください。", "dashboard.listing.readonlyNoItemsTitle": "表示するダッシュボードがありません", - "dashboard.listing.table.descriptionColumnName": "説明", "dashboard.listing.table.entityName": "ダッシュボード", "dashboard.listing.table.entityNamePlural": "ダッシュボード", - "dashboard.listing.table.titleColumnName": "タイトル", "dashboard.listing.unsaved.discardTitle": "変更を破棄", "dashboard.listing.unsaved.editTitle": "編集を続行", "dashboard.listing.unsaved.loading": "読み込み中", @@ -2325,7 +2323,6 @@ "discover.viewModes.document.label": "ドキュメント", "discover.viewModes.fieldStatistics.betaTitle": "ベータ", "discover.viewModes.fieldStatistics.label": "フィールド統計情報", - "discover.viewModes.legend": "表示モード", "embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} が追加されました", "embeddableApi.attributeService.saveToLibraryError": "保存中にエラーが発生しました。エラー:{errorMessage}", "embeddableApi.errors.embeddableFactoryNotFound": "{type} を読み込めません。Elasticsearch と Kibana のデフォルトのディストリビューションを適切なライセンスでアップグレードしてください。", @@ -4304,17 +4301,6 @@ "kibana-react.noDataPage.intro": "データを追加して開始するか、{solution}については{link}をご覧ください。", "kibana-react.noDataPage.welcomeTitle": "Elastic {solution}へようこそ。", "kibana-react.solutionNav.mobileTitleText": "{solutionName}メニュー", - "kibana-react.tableListView.listing.createNewItemButtonLabel": "Create {entityName}", - "kibana-react.tableListView.listing.deleteButtonMessage": "{itemCount} 件の {entityName} を削除", - "kibana-react.tableListView.listing.deleteConfirmModalDescription": "削除された {entityNamePlural} は復元できません。", - "kibana-react.tableListView.listing.deleteSelectedConfirmModal.title": "{itemCount} 件の {entityName} を削除", - "kibana-react.tableListView.listing.fetchErrorDescription": "{entityName}リストを取得できませんでした。{message}", - "kibana-react.tableListView.listing.listingLimitExceededDescription": "{totalItems} 件の {entityNamePlural} がありますが、{listingLimitText} の設定により {listingLimitValue} 件までしか下の表に表示できません。{advancedSettingsLink} の下でこの設定を変更できます。", - "kibana-react.tableListView.listing.listingLimitExceededDescriptionNoPermissions": "{totalItems} 件の {entityNamePlural} がありますが、{listingLimitText} の設定により {listingLimitValue} 件までしか下の表に表示できません。この設定を変更するには、システム管理者に問い合わせてください。", - "kibana-react.tableListView.listing.noAvailableItemsMessage": "利用可能な {entityNamePlural} がありません。", - "kibana-react.tableListView.listing.noMatchedItemsMessage": "検索条件に一致する {entityNamePlural} がありません。", - "kibana-react.tableListView.listing.table.editActionName": "{itemDescription}の編集", - "kibana-react.tableListView.listing.unableToDeleteDangerMessage": "{entityName} を削除できません", "kibana-react.dualRangeControl.maxInputAriaLabel": "範囲最大", "kibana-react.dualRangeControl.minInputAriaLabel": "範囲最小", "kibana-react.dualRangeControl.mustSetBothErrorMessage": "下と上の値の両方を設定する必要があります", @@ -4342,16 +4328,6 @@ "kibana-react.pageFooter.makeDefaultRouteLink": "これをランディングページにする", "kibana-react.solutionNav.collapsibleLabel": "サイドナビゲーションを折りたたむ", "kibana-react.solutionNav.openLabel": "サイドナビゲーションを開く", - "kibana-react.tableListView.lastUpdatedColumnTitle": "最終更新", - "kibana-react.tableListView.listing.deleteSelectedItemsConfirmModal.cancelButtonLabel": "キャンセル", - "kibana-react.tableListView.listing.deleteSelectedItemsConfirmModal.confirmButtonLabel": "削除", - "kibana-react.tableListView.listing.deleteSelectedItemsConfirmModal.confirmButtonLabelDeleting": "削除中", - "kibana-react.tableListView.listing.fetchErrorTitle": "リストを取得できませんでした", - "kibana-react.tableListView.listing.listingLimitExceeded.advancedSettingsLinkText": "高度な設定", - "kibana-react.tableListView.listing.listingLimitExceededTitle": "リスティング制限超過", - "kibana-react.tableListView.listing.table.actionTitle": "アクション", - "kibana-react.tableListView.listing.table.editActionDescription": "編集", - "kibana-react.tableListView.updatedDateUnknownLabel": "最終更新日が不明です", "management.landing.header": "Stack Management {version}へようこそ", "management.breadcrumb": "スタック管理", "management.landing.subhead": "インデックス、データビュー、保存されたオブジェクト、Kibanaの設定、その他を管理します。", @@ -6231,11 +6207,9 @@ "visualizations.listing.createNew.title": "最初のビジュアライゼーションの作成", "visualizations.listing.experimentalTitle": "テクニカルプレビュー", "visualizations.listing.experimentalTooltip": "この機能はテクニカルプレビュー中であり、将来のリリースでは変更されたり完全に削除されたりする場合があります。Elasticは最善の努力を講じてすべての問題の修正に努めますが、テクニカルプレビュー中の機能には正式なGA機能のサポートSLAが適用されません。", - "visualizations.listing.table.descriptionColumnName": "説明", "visualizations.listing.table.entityName": "ビジュアライゼーション", "visualizations.listing.table.entityNamePlural": "ビジュアライゼーション", "visualizations.listing.table.listTitle": "Visualizeライブラリ", - "visualizations.listing.table.titleColumnName": "タイトル", "visualizations.listing.table.typeColumnName": "型", "visualizations.listingPageTitle": "Visualizeライブラリ", "visualizations.missedDataView.dataViewReconfigure": "データビュー管理ページで再作成", @@ -6300,35 +6274,6 @@ "xpack.actions.actionTypeRegistry.register.invalidConnectorFeatureIds": "コネクタータイプ\"{connectorTypeId}\"の機能ID \"{ids}\"が無効です。", "xpack.actions.actionTypeRegistry.register.missingSupportedFeatureIds": "コネクタータイプ\"{connectorTypeId}\"に対して、1つ以上のsupportedFeatureId値を入力する必要があります。", "xpack.actions.apiAllowedHostsError": "コネクターアクションの構成エラー:{message}", - "xpack.actions.builtin.casesWebhook.casesWebhookConfigurationError": "ケースWebフックアクションの構成エラー:{err}", - "xpack.actions.builtin.casesWebhook.casesWebhookConfigurationErrorNoHostname": "ケースWebフックアクションの構成エラー:{url}を解析できません:{err}", - "xpack.actions.builtin.casesWebhook.configuration.apiAllowedHostsError": "コネクターアクションの構成エラー:{message}", - "xpack.actions.builtin.configuration.apiAllowedHostsError": "コネクターアクションの構成エラー:{message}", - "xpack.actions.builtin.configuration.apiValidateMissingOAuthFieldError": "isOAuth = {isOAuth}のときには、{field}を指定する必要があります", - "xpack.actions.builtin.configuration.apiValidateOAuthFieldError": "isOAuth = {isOAuth}のときには、{field}を指定しないでください", - "xpack.actions.builtin.email.customViewInKibanaMessage": "このメッセージは Kibana によって送信されました。[{kibanaFooterLinkText}]({link})。", - "xpack.actions.builtin.jira.configuration.apiAllowedHostsError": "コネクターアクションの構成エラー:{message}", - "xpack.actions.builtin.pagerduty.invalidTimestampErrorMessage": "タイムスタンプ\"{timestamp}\"の解析エラー", - "xpack.actions.builtin.pagerduty.missingDedupkeyErrorMessage": "eventActionが「{eventAction}」のときにはDedupKeyが必要です", - "xpack.actions.builtin.pagerduty.pagerdutyConfigurationError": "pagerduty アクションの設定エラー:{message}", - "xpack.actions.builtin.pagerduty.postingRetryErrorMessage": "pagerduty イベントの投稿エラー:http status {status}、後ほど再試行", - "xpack.actions.builtin.pagerduty.postingUnexpectedErrorMessage": "pagerduty イベントの投稿エラー:予期せぬステータス {status}", - "xpack.actions.builtin.pagerduty.timestampParsingFailedErrorMessage": "タイムスタンプの解析エラー \"{timestamp}\":{message}", - "xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage": "slack メッセージの投稿エラー、 {retryString} で再試行", - "xpack.actions.builtin.slack.slackConfigurationError": "slack アクションの設定エラー:{message}", - "xpack.actions.builtin.slack.unexpectedHttpResponseErrorMessage": "slack からの予期せぬ http 応答:{httpStatus} {httpStatusText}", - "xpack.actions.builtin.swimlane.configuration.apiAllowedHostsError": "コネクターアクションの構成エラー:{message}", - "xpack.actions.builtin.teams.errorPostingRetryDateErrorMessage": "Microsoft Teams メッセージの投稿エラーです。{retryString} に再試行します", - "xpack.actions.builtin.teams.teamsConfigurationError": "Teams アクションの設定エラー:{message}", - "xpack.actions.builtin.webhook.invalidResponseRetryDateErrorMessage": "Webフックの呼び出しエラー、{retryString} に再試行", - "xpack.actions.builtin.webhook.webhookConfigurationError": "Web フックアクションの構成中にエラーが発生:{message}", - "xpack.actions.builtin.webhook.webhookConfigurationErrorNoHostname": "Webフックアクションの構成エラーです。URLを解析できません。{err}", - "xpack.actions.builtin.xmatters.postingRetryErrorMessage": "xMattersフローのトリガーエラー:HTTPステータス{status}。しばらくたってから再試行してください", - "xpack.actions.builtin.xmatters.unexpectedStatusErrorMessage": "xMattersフローのトリガーエラー:予期しないステータス{status}", - "xpack.actions.builtin.xmatters.xmattersConfigurationError": "xMattersアクションの設定エラー:{message}", - "xpack.actions.builtin.xmatters.xmattersConfigurationErrorNoHostname": "xMattersアクションの構成エラー:URLを解析できません:{err}", - "xpack.actions.builtin.xmatters.xmattersHostnameNotAllowed": "{message}", - "xpack.actions.builtin.xmatters.xmattersInvalidUrlError": "無効なsecretsUrl:{err}", "xpack.actions.disabledActionTypeError": "アクションタイプ \"{actionType}\" は、Kibana 構成 xpack.actions.enabledActionTypes では有効化されません", "xpack.actions.savedObjects.onImportText": "{connectorsWithSecretsLength} {connectorsWithSecretsLength, plural, other {コネクターには}}更新が必要な機密情報があります。", "xpack.actions.serverSideErrors.expirerdLicenseErrorMessage": "{licenseType} ライセンスの期限が切れたのでアクションタイプ {actionTypeId} は無効です。", @@ -6343,60 +6288,89 @@ "xpack.actions.availableConnectorFeatures.cases": "ケース", "xpack.actions.availableConnectorFeatures.securitySolution": "セキュリティソリューション", "xpack.actions.availableConnectorFeatures.uptime": "アップタイム", - "xpack.actions.builtin.case.swimlaneTitle": "スイムレーン", - "xpack.actions.builtin.cases.casesWebhookTitle": "Webフック - ケース管理", - "xpack.actions.builtin.cases.jiraTitle": "Jira", - "xpack.actions.builtin.cases.resilientTitle": "IBM Resilient", - "xpack.actions.builtin.casesWebhook.invalidUsernamePassword": "ユーザーとパスワードの両方を指定する必要があります", - "xpack.actions.builtin.configuration.apiBasicAuthCredentialsError": "ユーザーとパスワードの両方を指定する必要があります", - "xpack.actions.builtin.configuration.apiCredentialsError": "基本認証資格情報またはOAuth資格情報を指定する必要があります。", - "xpack.actions.builtin.configuration.apiOAuthCredentialsError": "clientSecretおよびprivateKeyの両方を指定する必要があります", - "xpack.actions.builtin.email.errorSendingErrorMessage": "エラー送信メールアドレス", - "xpack.actions.builtin.email.kibanaFooterLinkText": "Kibana を開く", - "xpack.actions.builtin.email.sentByKibanaMessage": "このメッセージは Kibana によって送信されました。", - "xpack.actions.builtin.emailTitle": "メール", - "xpack.actions.builtin.esIndex.errorIndexingErrorMessage": "エラーインデックス作成ドキュメント", - "xpack.actions.builtin.esIndexTitle": "インデックス", - "xpack.actions.builtin.pagerduty.postingErrorMessage": "pagerduty イベントの投稿エラー", - "xpack.actions.builtin.pagerdutyTitle": "PagerDuty", - "xpack.actions.builtin.serverLog.errorLoggingErrorMessage": "メッセージのロギングエラー", - "xpack.actions.builtin.serverLogTitle": "サーバーログ", - "xpack.actions.builtin.serviceNowITOMTitle": "ServiceNow ITOM", - "xpack.actions.builtin.serviceNowITSMTitle": "ServiceNow ITSM", - "xpack.actions.builtin.serviceNowSIRTitle": "ServiceNow SecOps", - "xpack.actions.builtin.serviceNowTitle": "ServiceNow", - "xpack.actions.builtin.slack.errorPostingErrorMessage": "slack メッセージの投稿エラー", - "xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage": "slack メッセージの投稿エラー、後ほど再試行", - "xpack.actions.builtin.slack.slackConfigurationErrorNoHostname": "slack アクションの構成エラー:Web フック URL からホスト名をパースできません", - "xpack.actions.builtin.slack.unexpectedNullResponseErrorMessage": "Slack から予期せぬ null 応答", - "xpack.actions.builtin.slackTitle": "Slack", - "xpack.actions.builtin.swimlaneTitle": "スイムレーン", - "xpack.actions.builtin.teams.errorPostingRetryLaterErrorMessage": "Microsoft Teams メッセージの投稿エラーです。しばらくたってから再試行します", - "xpack.actions.builtin.teams.invalidResponseErrorMessage": "Microsoft Teams への投稿エラーです。無効な応答です", - "xpack.actions.builtin.teams.teamsConfigurationErrorNoHostname": "Teams アクションの構成エラー:Web フック URL からホスト名をパースできません", - "xpack.actions.builtin.teams.unreachableErrorMessage": "Microsoft Teams への投稿エラーです。予期しないエラーです", - "xpack.actions.builtin.teamsTitle": "Microsoft Teams", - "xpack.actions.builtin.webhook.invalidResponseErrorMessage": "Webフックの呼び出しエラー、無効な応答", - "xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage": "Webフックの呼び出しエラー、後ほど再試行", - "xpack.actions.builtin.webhook.invalidUsernamePassword": "ユーザーとパスワードの両方を指定する必要があります", - "xpack.actions.builtin.webhook.requestFailedErrorMessage": "Webフックの呼び出しエラー。要求が失敗しました", - "xpack.actions.builtin.webhook.unreachableErrorMessage": "webhookの呼び出しエラー、予期せぬエラー", - "xpack.actions.builtin.webhookTitle": "Web フック", - "xpack.actions.builtin.xmatters.invalidUsernamePassword": "ユーザーとパスワードの両方を指定する必要があります。", - "xpack.actions.builtin.xmatters.missingConfigUrl": "有効なconfigUrlを指定してください", - "xpack.actions.builtin.xmatters.missingPassword": "有効なパスワードを指定してください", - "xpack.actions.builtin.xmatters.missingSecretsUrl": "有効なsecretsUrlとAPIキーを指定してください", - "xpack.actions.builtin.xmatters.missingUser": "有効なユーザー名を指定してください", - "xpack.actions.builtin.xmatters.noSecretsProvided": "認証するには、secretsUrlリンクまたはユーザー/パスワードを指定してください", - "xpack.actions.builtin.xmatters.noUserPassWhenSecretsUrl": "URL認証ではユーザー/パスワードを使用できません。有効なsecretsUrlを指定するか、基本認証を使用してください。", - "xpack.actions.builtin.xmatters.postingErrorMessage": "xMattersワークフローのトリガーエラー", - "xpack.actions.builtin.xmatters.shouldNotHaveConfigUrl": "usesBasicがfalseのときには、configUrlを指定しないでください", - "xpack.actions.builtin.xmatters.shouldNotHaveSecretsUrl": "usesBasicがtrueのときには、secretsUrlを指定しないでください", - "xpack.actions.builtin.xmatters.shouldNotHaveUsernamePassword": "usesBasicがfalseのときには、ユーザー名とパスワードを指定しないでください", - "xpack.actions.builtin.xmattersTitle": "xMatters", "xpack.actions.featureRegistry.actionsFeatureName": "アクションとコネクター", "xpack.actions.savedObjects.goToConnectorsButtonText": "コネクターに移動", "xpack.actions.serverSideErrors.unavailableLicenseInformationErrorMessage": "グラフを利用できません。現在ライセンス情報が利用できません。", + "xpack.stackConnectors.casesWebhook.configurationError": "ケースWebフックアクションの構成エラー:{err}", + "xpack.stackConnectors.casesWebhook.configurationErrorNoHostname": "ケースWebフックアクションの構成エラー:{url}を解析できません:{err}", + "xpack.stackConnectors.casesWebhook.configuration.apiAllowedHostsError": "コネクターアクションの構成エラー:{message}", + "xpack.stackConnectors.resilient.configuration.apiAllowedHostsError": "コネクターアクションの構成エラー:{message}", + "xpack.stackConnectors.serviceNow.configuration.apiAllowedHostsError": "コネクターアクションの構成エラー:{message}", + "xpack.stackConnectors.serviceNow.configuration.apiValidateMissingOAuthFieldError": "isOAuth = {isOAuth}のときには、{field}を指定する必要があります", + "xpack.stackConnectors.serviceNow.configuration.apiValidateOAuthFieldError": "isOAuth = {isOAuth}のときには、{field}を指定しないでください", + "xpack.stackConnectors.email.customViewInKibanaMessage": "このメッセージは Kibana によって送信されました。[{kibanaFooterLinkText}]({link})。", + "xpack.stackConnectors.jira.configuration.apiAllowedHostsError": "コネクターアクションの構成エラー:{message}", + "xpack.stackConnectors.pagerduty.invalidTimestampErrorMessage": "タイムスタンプ\"{timestamp}\"の解析エラー", + "xpack.stackConnectors.pagerduty.missingDedupkeyErrorMessage": "eventActionが「{eventAction}」のときにはDedupKeyが必要です", + "xpack.stackConnectors.pagerduty.configurationError": "pagerduty アクションの設定エラー:{message}", + "xpack.stackConnectors.pagerduty.postingRetryErrorMessage": "pagerduty イベントの投稿エラー:http status {status}、後ほど再試行", + "xpack.stackConnectors.pagerduty.postingUnexpectedErrorMessage": "pagerduty イベントの投稿エラー:予期せぬステータス {status}", + "xpack.stackConnectors.pagerduty.timestampParsingFailedErrorMessage": "タイムスタンプの解析エラー \"{timestamp}\":{message}", + "xpack.stackConnectors.slack.errorPostingRetryDateErrorMessage": "slack メッセージの投稿エラー、 {retryString} で再試行", + "xpack.stackConnectors.slack.configurationError": "slack アクションの設定エラー:{message}", + "xpack.stackConnectors.slack.unexpectedHttpResponseErrorMessage": "slack からの予期せぬ http 応答:{httpStatus} {httpStatusText}", + "xpack.stackConnectors.swimlane.configuration.apiAllowedHostsError": "コネクターアクションの構成エラー:{message}", + "xpack.stackConnectors.teams.errorPostingRetryDateErrorMessage": "Microsoft Teams メッセージの投稿エラーです。{retryString} に再試行します", + "xpack.stackConnectors.teams.configurationError": "Teams アクションの設定エラー:{message}", + "xpack.stackConnectors.webhook.invalidResponseRetryDateErrorMessage": "Webフックの呼び出しエラー、{retryString} に再試行", + "xpack.stackConnectors.webhook.configurationError": "Web フックアクションの構成中にエラーが発生:{message}", + "xpack.stackConnectors.webhook.configurationErrorNoHostname": "Webフックアクションの構成エラーです。URLを解析できません。{err}", + "xpack.stackConnectors.xmatters.postingRetryErrorMessage": "xMattersフローのトリガーエラー:HTTPステータス{status}。しばらくたってから再試行してください", + "xpack.stackConnectors.xmatters.unexpectedStatusErrorMessage": "xMattersフローのトリガーエラー:予期しないステータス{status}", + "xpack.stackConnectors.xmatters.configurationError": "xMattersアクションの設定エラー:{message}", + "xpack.stackConnectors.xmatters.configurationErrorNoHostname": "xMattersアクションの構成エラー:URLを解析できません:{err}", + "xpack.stackConnectors.xmatters.hostnameNotAllowed": "{message}", + "xpack.stackConnectors.xmatters.invalidUrlError": "無効なsecretsUrl:{err}", + "xpack.stackConnectors.casesWebhook.title": "Webフック - ケース管理", + "xpack.stackConnectors.jira.title": "Jira", + "xpack.stackConnectors.resilient.title": "IBM Resilient", + "xpack.stackConnectors.casesWebhook.invalidUsernamePassword": "ユーザーとパスワードの両方を指定する必要があります", + "xpack.stackConnectors.serviceNow.configuration.apiBasicAuthCredentialsError": "ユーザーとパスワードの両方を指定する必要があります", + "xpack.stackConnectors.serviceNow.configuration.apiCredentialsError": "基本認証資格情報またはOAuth資格情報を指定する必要があります。", + "xpack.stackConnectors.serviceNow.configuration.apiOAuthCredentialsError": "clientSecretおよびprivateKeyの両方を指定する必要があります", + "xpack.stackConnectors.email.errorSendingErrorMessage": "エラー送信メールアドレス", + "xpack.stackConnectors.email.kibanaFooterLinkText": "Kibana を開く", + "xpack.stackConnectors.email.sentByKibanaMessage": "このメッセージは Kibana によって送信されました。", + "xpack.stackConnectors.email.title": "メール", + "xpack.stackConnectors.esIndex.errorIndexingErrorMessage": "エラーインデックス作成ドキュメント", + "xpack.stackConnectors.esIndex.title": "インデックス", + "xpack.stackConnectors.pagerduty.postingErrorMessage": "pagerduty イベントの投稿エラー", + "xpack.stackConnectors.pagerduty.title": "PagerDuty", + "xpack.stackConnectors.serverLog.errorLoggingErrorMessage": "メッセージのロギングエラー", + "xpack.stackConnectors.serverLog.title": "サーバーログ", + "xpack.stackConnectors.serviceNowITOM.title": "ServiceNow ITOM", + "xpack.stackConnectors.serviceNowITSM.title": "ServiceNow ITSM", + "xpack.stackConnectors.serviceNowSIR.title": "ServiceNow SecOps", + "xpack.stackConnectors.serviceNow.title": "ServiceNow", + "xpack.stackConnectors.slack.errorPostingErrorMessage": "slack メッセージの投稿エラー", + "xpack.stackConnectors.slack.errorPostingRetryLaterErrorMessage": "slack メッセージの投稿エラー、後ほど再試行", + "xpack.stackConnectors.slack.configurationErrorNoHostname": "slack アクションの構成エラー:Web フック URL からホスト名をパースできません", + "xpack.stackConnectors.slack.unexpectedNullResponseErrorMessage": "Slack から予期せぬ null 応答", + "xpack.stackConnectors.slack.title": "Slack", + "xpack.stackConnectors.swimlane.title": "スイムレーン", + "xpack.stackConnectors.teams.errorPostingRetryLaterErrorMessage": "Microsoft Teams メッセージの投稿エラーです。しばらくたってから再試行します", + "xpack.stackConnectors.teams.invalidResponseErrorMessage": "Microsoft Teams への投稿エラーです。無効な応答です", + "xpack.stackConnectors.teams.configurationErrorNoHostname": "Teams アクションの構成エラー:Web フック URL からホスト名をパースできません", + "xpack.stackConnectors.teams.unreachableErrorMessage": "Microsoft Teams への投稿エラーです。予期しないエラーです", + "xpack.stackConnectors.teams.title": "Microsoft Teams", + "xpack.stackConnectors.webhook.invalidResponseErrorMessage": "Webフックの呼び出しエラー、無効な応答", + "xpack.stackConnectors.webhook.invalidResponseRetryLaterErrorMessage": "Webフックの呼び出しエラー、後ほど再試行", + "xpack.stackConnectors.webhook.invalidUsernamePassword": "ユーザーとパスワードの両方を指定する必要があります", + "xpack.stackConnectors.webhook.requestFailedErrorMessage": "Webフックの呼び出しエラー。要求が失敗しました", + "xpack.stackConnectors.webhook.unreachableErrorMessage": "webhookの呼び出しエラー、予期せぬエラー", + "xpack.stackConnectors.webhook.title": "Web フック", + "xpack.stackConnectors.xmatters.invalidUsernamePassword": "ユーザーとパスワードの両方を指定する必要があります。", + "xpack.stackConnectors.xmatters.missingConfigUrl": "有効なconfigUrlを指定してください", + "xpack.stackConnectors.xmatters.missingPassword": "有効なパスワードを指定してください", + "xpack.stackConnectors.xmatters.missingSecretsUrl": "有効なsecretsUrlとAPIキーを指定してください", + "xpack.stackConnectors.xmatters.missingUser": "有効なユーザー名を指定してください", + "xpack.stackConnectors.xmatters.noSecretsProvided": "認証するには、secretsUrlリンクまたはユーザー/パスワードを指定してください", + "xpack.stackConnectors.xmatters.noUserPassWhenSecretsUrl": "URL認証ではユーザー/パスワードを使用できません。有効なsecretsUrlを指定するか、基本認証を使用してください。", + "xpack.stackConnectors.xmatters.postingErrorMessage": "xMattersワークフローのトリガーエラー", + "xpack.stackConnectors.xmatters.shouldNotHaveConfigUrl": "usesBasicがfalseのときには、configUrlを指定しないでください", + "xpack.stackConnectors.xmatters.shouldNotHaveSecretsUrl": "usesBasicがtrueのときには、secretsUrlを指定しないでください", + "xpack.stackConnectors.xmatters.shouldNotHaveUsernamePassword": "usesBasicがfalseのときには、ユーザー名とパスワードを指定しないでください", + "xpack.stackConnectors.xmatters.title": "xMatters", "xpack.aiops.explainLogRateSpikes.loadingState.identifiedFieldCandidates": "{fieldCandidatesCount, plural, other {# 個のフィールド候補}}が特定されました。", "xpack.aiops.explainLogRateSpikes.loadingState.identifiedFieldValuePairs": "{fieldValuePairsCount, plural, other {# 個の重要なフィールド/値のペア}}が特定されました。", "xpack.aiops.index.dataLoader.internalServerErrorMessage": "インデックス {index} のデータの読み込み中にエラーが発生。{message}。リクエストがタイムアウトした可能性があります。小さなサンプルサイズを使うか、時間範囲を狭めてみてください。", @@ -7210,12 +7184,7 @@ "xpack.apm.serviceIcons.serviceDetails.cloud.projectIdLabel": "プロジェクト ID", "xpack.apm.serviceIcons.serviceDetails.cloud.providerLabel": "クラウドプロバイダー", "xpack.apm.serviceIcons.serviceDetails.cloud.serviceNameLabel": "クラウドサービス", - "xpack.apm.serviceIcons.serviceDetails.container.containerizedLabel": "コンテナー化", - "xpack.apm.serviceIcons.serviceDetails.container.noLabel": "いいえ", - "xpack.apm.serviceIcons.serviceDetails.container.orchestrationLabel": "オーケストレーション", - "xpack.apm.serviceIcons.serviceDetails.container.osLabel": "OS", "xpack.apm.serviceIcons.serviceDetails.container.totalNumberInstancesLabel": "インスタンスの合計数", - "xpack.apm.serviceIcons.serviceDetails.container.yesLabel": "はい", "xpack.apm.serviceIcons.serviceDetails.service.agentLabel": "エージェント名・バージョン", "xpack.apm.serviceIcons.serviceDetails.service.frameworkLabel": "フレームワーク名", "xpack.apm.serviceIcons.serviceDetails.service.runtimeLabel": "ランタイム名・バージョン", @@ -9581,7 +9550,6 @@ "xpack.csp.cloudPosturePage.errorRenderer.errorTitle": "クラウドセキュリティ態勢データを取得できませんでした", "xpack.csp.cloudPosturePage.loadingDescription": "読み込み中...", "xpack.csp.cloudPosturePage.packageNotInstalled.buttonLabel": "CIS統合を追加", - "xpack.csp.cloudPosturePage.packageNotInstalled.description": "CIS Kubernetes Benchmark統合は、CISの推奨事項に照らしてKubernetesクラスター設定を測定します。", "xpack.csp.cloudPosturePage.packageNotInstalled.pageTitle": "開始するには統合をインストールしてください", "xpack.csp.cloudPosturePage.packageNotInstalled.solutionNameLabel": "クラウドセキュリティ態勢", "xpack.csp.cspEvaluationBadge.failLabel": "失敗", @@ -13613,10 +13581,8 @@ "xpack.graph.listing.graphsTitle": "グラフ", "xpack.graph.listing.noDataSource.sampleDataInstallLinkText": "サンプルデータ", "xpack.graph.listing.noItemsMessage": "グラフがないようです。", - "xpack.graph.listing.table.descriptionColumnName": "説明", "xpack.graph.listing.table.entityName": "グラフ", "xpack.graph.listing.table.entityNamePlural": "グラフ", - "xpack.graph.listing.table.titleColumnName": "タイトル", "xpack.graph.missingWorkspaceErrorMessage": "ID でグラフを読み込めませんでした", "xpack.graph.newGraphTitle": "保存されていないグラフ", "xpack.graph.noDataSourceNotificationMessageText.managementDataViewLinkText": "管理 > データビュー", @@ -17808,7 +17774,6 @@ "xpack.lens.xyChart.addLayerTooltip": "複数のレイヤーを使用すると、ビジュアライゼーションタイプを組み合わせたり、別のデータビューを可視化したりすることができます。", "xpack.lens.xyChart.addReferenceLineLayerLabel": "基準線", "xpack.lens.xyChart.addReferenceLineLayerLabelDisabledHelp": "一部のデータを追加して、基準レイヤーを有効にする", - "xpack.lens.xyChart.annotation.name": "注釈を非表示", "xpack.lens.xyChart.annotationDate": "注釈日", "xpack.lens.xyChart.annotationDate.from": "開始:", "xpack.lens.xyChart.annotationDate.to": "終了:", @@ -18442,12 +18407,9 @@ "xpack.maps.map.initializeErrorTitle": "マップを初期化できません", "xpack.maps.mapActions.addFeatureError": "機能をインデックスに追加できません。", "xpack.maps.mapActions.removeFeatureError": "インデックスから機能を削除できません。", - "xpack.maps.mapListing.descriptionFieldTitle": "説明", "xpack.maps.mapListing.entityName": "マップ", "xpack.maps.mapListing.entityNamePlural": "マップ", "xpack.maps.mapListing.errorAttemptingToLoadSavedMaps": "マップを読み込めません", - "xpack.maps.mapListing.tableCaption": "マップ", - "xpack.maps.mapListing.titleFieldTitle": "タイトル", "xpack.maps.mapSavedObjectLabel": "マップ", "xpack.maps.mapSettingsPanel.addCustomIcon": "カスタムアイコンを追加", "xpack.maps.mapSettingsPanel.autoFitToBoundsLocationLabel": "自動的にマップをデータ境界に合わせる", @@ -25553,11 +25515,9 @@ "xpack.securitySolution.administration.os.windows": "Windows", "xpack.securitySolution.alertDetails.enrichmentQueryEndDate": "終了日", "xpack.securitySolution.alertDetails.enrichmentQueryStartDate": "開始日", - "xpack.securitySolution.alertDetails.hostRiskClassification": "ホストリスク分類", "xpack.securitySolution.alertDetails.investigationTimeQueryTitle": "Threat Intelligenceで拡張", "xpack.securitySolution.alertDetails.noEnrichmentsFoundDescription": "指標一致ルールのいずれかまたはこのアラートの拡張と一致する脅威インテリジェンスが見つかりませんでした。", "xpack.securitySolution.alertDetails.noInvestigationEnrichmentsDescription": "デフォルトで過去30日間に検索した脅威インテリジェンスソースから使用可能な追加情報がフィールド値にはないことがわかりました。", - "xpack.securitySolution.alertDetails.noRiskDataDescription": "このアラートのホストリスクデータが見つかりません", "xpack.securitySolution.alertDetails.overview": "概要", "xpack.securitySolution.alertDetails.overview.enrichedDataTitle": "強化されたデータ", "xpack.securitySolution.alertDetails.overview.highlightedFields": "ハイライトされたフィールド", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index fe7acd6cc4e32..06a9721b830a4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -972,10 +972,8 @@ "dashboard.listing.createNewDashboard.title": "创建您的首个仪表板", "dashboard.listing.readonlyNoItemsBody": "没有可用的仪表板。要更改您的权限以查看此工作区中的仪表板,请联系管理员。", "dashboard.listing.readonlyNoItemsTitle": "没有可查看的仪表板", - "dashboard.listing.table.descriptionColumnName": "描述", "dashboard.listing.table.entityName": "仪表板", "dashboard.listing.table.entityNamePlural": "仪表板", - "dashboard.listing.table.titleColumnName": "标题", "dashboard.listing.unsaved.discardTitle": "放弃更改", "dashboard.listing.unsaved.editTitle": "继续编辑", "dashboard.listing.unsaved.loading": "正在加载", @@ -2329,7 +2327,6 @@ "discover.viewModes.document.label": "文档", "discover.viewModes.fieldStatistics.betaTitle": "公测版", "discover.viewModes.fieldStatistics.label": "字段统计信息", - "discover.viewModes.legend": "视图模式", "embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} 已添加", "embeddableApi.attributeService.saveToLibraryError": "保存时出错。错误:{errorMessage}", "embeddableApi.errors.embeddableFactoryNotFound": "{type} 无法加载。请升级到具有适当许可的默认 Elasticsearch 和 Kibana 分发。", @@ -4309,17 +4306,6 @@ "kibana-react.noDataPage.intro": "添加您的数据以开始,或{link}{solution}。", "kibana-react.noDataPage.welcomeTitle": "欢迎使用 Elastic {solution}!", "kibana-react.solutionNav.mobileTitleText": "{solutionName} 菜单", - "kibana-react.tableListView.listing.createNewItemButtonLabel": "创建 {entityName}", - "kibana-react.tableListView.listing.deleteButtonMessage": "删除 {itemCount} 个 {entityName}", - "kibana-react.tableListView.listing.deleteConfirmModalDescription": "无法恢复删除的 {entityNamePlural}。", - "kibana-react.tableListView.listing.deleteSelectedConfirmModal.title": "删除 {itemCount} 个 {entityName}?", - "kibana-react.tableListView.listing.fetchErrorDescription": "无法提取 {entityName} 列表:{message}。", - "kibana-react.tableListView.listing.listingLimitExceededDescription": "您有 {totalItems} 个{entityNamePlural},但您的“{listingLimitText}”设置阻止下表显示 {listingLimitValue} 个以上。您可以在“{advancedSettingsLink}”下更改此设置。", - "kibana-react.tableListView.listing.listingLimitExceededDescriptionNoPermissions": "您有 {totalItems} 个{entityNamePlural},但您的“{listingLimitText}”设置阻止下表显示 {listingLimitValue} 个以上。请联系系统管理员更改此设置。", - "kibana-react.tableListView.listing.noAvailableItemsMessage": "没有可用的{entityNamePlural}。", - "kibana-react.tableListView.listing.noMatchedItemsMessage": "没有任何{entityNamePlural}匹配您的搜索。", - "kibana-react.tableListView.listing.table.editActionName": "编辑 {itemDescription}", - "kibana-react.tableListView.listing.unableToDeleteDangerMessage": "无法删除{entityName}", "kibana-react.dualRangeControl.maxInputAriaLabel": "范围最大值", "kibana-react.dualRangeControl.minInputAriaLabel": "范围最小值", "kibana-react.dualRangeControl.mustSetBothErrorMessage": "下限值和上限值都须设置", @@ -4347,16 +4333,6 @@ "kibana-react.pageFooter.makeDefaultRouteLink": "将此设为我的登陆页面", "kibana-react.solutionNav.collapsibleLabel": "折叠侧边导航", "kibana-react.solutionNav.openLabel": "打开侧边导航", - "kibana-react.tableListView.lastUpdatedColumnTitle": "上次更新时间", - "kibana-react.tableListView.listing.deleteSelectedItemsConfirmModal.cancelButtonLabel": "取消", - "kibana-react.tableListView.listing.deleteSelectedItemsConfirmModal.confirmButtonLabel": "删除", - "kibana-react.tableListView.listing.deleteSelectedItemsConfirmModal.confirmButtonLabelDeleting": "正在删除", - "kibana-react.tableListView.listing.fetchErrorTitle": "提取列表失败", - "kibana-react.tableListView.listing.listingLimitExceeded.advancedSettingsLinkText": "高级设置", - "kibana-react.tableListView.listing.listingLimitExceededTitle": "已超过列表限制", - "kibana-react.tableListView.listing.table.actionTitle": "操作", - "kibana-react.tableListView.listing.table.editActionDescription": "编辑", - "kibana-react.tableListView.updatedDateUnknownLabel": "上次更新时间未知", "management.landing.header": "欢迎使用 Stack Management {version}", "management.breadcrumb": "Stack Management", "management.landing.subhead": "管理您的索引、数据视图、已保存对象、Kibana 设置等等。", @@ -6238,11 +6214,9 @@ "visualizations.listing.createNew.title": "创建您的首个可视化", "visualizations.listing.experimentalTitle": "技术预览", "visualizations.listing.experimentalTooltip": "此功能处于技术预览状态,在未来版本中可能会更改或完全移除。Elastic 将尽最大努力来修复任何问题,但处于技术预览状态的功能不受正式 GA 功能支持 SLA 的约束。", - "visualizations.listing.table.descriptionColumnName": "描述", "visualizations.listing.table.entityName": "可视化", "visualizations.listing.table.entityNamePlural": "可视化", "visualizations.listing.table.listTitle": "Visualize 库", - "visualizations.listing.table.titleColumnName": "标题", "visualizations.listing.table.typeColumnName": "类型", "visualizations.listingPageTitle": "Visualize 库", "visualizations.missedDataView.dataViewReconfigure": "在数据视图管理页面中重新创建", @@ -6307,35 +6281,6 @@ "xpack.actions.actionTypeRegistry.register.invalidConnectorFeatureIds": "连接器类型“{connectorTypeId}”的功能 ID“{ids}”无效。", "xpack.actions.actionTypeRegistry.register.missingSupportedFeatureIds": "必须至少为连接器类型“{connectorTypeId}”提供一个“supportedFeatureId”值。", "xpack.actions.apiAllowedHostsError": "配置连接器操作时出错:{message}", - "xpack.actions.builtin.casesWebhook.casesWebhookConfigurationError": "配置案例 Webhook 操作时出错:{err}", - "xpack.actions.builtin.casesWebhook.casesWebhookConfigurationErrorNoHostname": "配置案例 Webhook 操作时出错:无法解析 {url}:{err}", - "xpack.actions.builtin.casesWebhook.configuration.apiAllowedHostsError": "配置连接器操作时出错:{message}", - "xpack.actions.builtin.configuration.apiAllowedHostsError": "配置连接器操作时出错:{message}", - "xpack.actions.builtin.configuration.apiValidateMissingOAuthFieldError": "isOAuth = {isOAuth} 时,必须提供 {field}", - "xpack.actions.builtin.configuration.apiValidateOAuthFieldError": "不得与 isOAuth = {isOAuth} 一起提供 {field}", - "xpack.actions.builtin.email.customViewInKibanaMessage": "此消息由 Kibana 发送。[{kibanaFooterLinkText}]({link})。", - "xpack.actions.builtin.jira.configuration.apiAllowedHostsError": "配置连接器操作时出错:{message}", - "xpack.actions.builtin.pagerduty.invalidTimestampErrorMessage": "解析时间戳“{timestamp}”时出错", - "xpack.actions.builtin.pagerduty.missingDedupkeyErrorMessage": "当 eventAction 是“{eventAction}”时需要 DedupKey", - "xpack.actions.builtin.pagerduty.pagerdutyConfigurationError": "配置 pagerduty 操作时出错:{message}", - "xpack.actions.builtin.pagerduty.postingRetryErrorMessage": "发布 pagerduty 事件时出错:http 状态 {status},请稍后重试", - "xpack.actions.builtin.pagerduty.postingUnexpectedErrorMessage": "发布 pagerduty 事件时出错:非预期状态 {status}", - "xpack.actions.builtin.pagerduty.timestampParsingFailedErrorMessage": "解析时间戳“{timestamp}”出错:{message}", - "xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage": "发布 Slack 消息时出错,在 {retryString} 重试", - "xpack.actions.builtin.slack.slackConfigurationError": "配置 slack 操作时出错:{message}", - "xpack.actions.builtin.slack.unexpectedHttpResponseErrorMessage": "来自 slack 的非预期 http 响应:{httpStatus} {httpStatusText}", - "xpack.actions.builtin.swimlane.configuration.apiAllowedHostsError": "配置连接器操作时出错:{message}", - "xpack.actions.builtin.teams.errorPostingRetryDateErrorMessage": "发布 Microsoft Teams 消息时出错,请在 {retryString} 重试", - "xpack.actions.builtin.teams.teamsConfigurationError": "配置 Teams 操作时出错:{message}", - "xpack.actions.builtin.webhook.invalidResponseRetryDateErrorMessage": "调用 webhook 时出错,请在 {retryString} 重试", - "xpack.actions.builtin.webhook.webhookConfigurationError": "配置 Webhook 操作时出错:{message}", - "xpack.actions.builtin.webhook.webhookConfigurationErrorNoHostname": "配置 Webhook 操作时出错:无法解析 url:{err}", - "xpack.actions.builtin.xmatters.postingRetryErrorMessage": "触发 xMatters 流时出错:http 状态为 {status},请稍后重试", - "xpack.actions.builtin.xmatters.unexpectedStatusErrorMessage": "触发 xMatters 流时出错:非预期状态 {status}", - "xpack.actions.builtin.xmatters.xmattersConfigurationError": "配置 xMatters 操作时出错:{message}", - "xpack.actions.builtin.xmatters.xmattersConfigurationErrorNoHostname": "配置 xMatters 操作时出错:无法解析 url:{err}", - "xpack.actions.builtin.xmatters.xmattersHostnameNotAllowed": "{message}", - "xpack.actions.builtin.xmatters.xmattersInvalidUrlError": "secretsUrl 无效:{err}", "xpack.actions.disabledActionTypeError": "操作类型“{actionType}”在 Kibana 配置 xpack.actions.enabledActionTypes 中未启用", "xpack.actions.savedObjects.onImportText": "{connectorsWithSecretsLength} 个{connectorsWithSecretsLength, plural, other {连接器具有}}需要更新的敏感信息。", "xpack.actions.serverSideErrors.expirerdLicenseErrorMessage": "操作类型 {actionTypeId} 已禁用,因为您的{licenseType}许可证已过期。", @@ -6350,60 +6295,89 @@ "xpack.actions.availableConnectorFeatures.cases": "案例", "xpack.actions.availableConnectorFeatures.securitySolution": "安全解决方案", "xpack.actions.availableConnectorFeatures.uptime": "运行时间", - "xpack.actions.builtin.case.swimlaneTitle": "泳道", - "xpack.actions.builtin.cases.casesWebhookTitle": "Webhook - 案例管理", - "xpack.actions.builtin.cases.jiraTitle": "Jira", - "xpack.actions.builtin.cases.resilientTitle": "IBM Resilient", - "xpack.actions.builtin.casesWebhook.invalidUsernamePassword": "必须指定用户及密码", - "xpack.actions.builtin.configuration.apiBasicAuthCredentialsError": "必须同时指定用户名和密码", - "xpack.actions.builtin.configuration.apiCredentialsError": "必须指定基本身份验证或 OAuth 凭据", - "xpack.actions.builtin.configuration.apiOAuthCredentialsError": "必须同时指定 clientSecret 和 privateKey", - "xpack.actions.builtin.email.errorSendingErrorMessage": "发送电子邮件时出错", - "xpack.actions.builtin.email.kibanaFooterLinkText": "前往 Kibana", - "xpack.actions.builtin.email.sentByKibanaMessage": "此消息由 Kibana 发送。", - "xpack.actions.builtin.emailTitle": "电子邮件", - "xpack.actions.builtin.esIndex.errorIndexingErrorMessage": "索引文档时出错", - "xpack.actions.builtin.esIndexTitle": "索引", - "xpack.actions.builtin.pagerduty.postingErrorMessage": "发布 pagerduty 事件时出错", - "xpack.actions.builtin.pagerdutyTitle": "PagerDuty", - "xpack.actions.builtin.serverLog.errorLoggingErrorMessage": "记录消息时出错", - "xpack.actions.builtin.serverLogTitle": "服务器日志", - "xpack.actions.builtin.serviceNowITOMTitle": "ServiceNow ITOM", - "xpack.actions.builtin.serviceNowITSMTitle": "ServiceNow ITSM", - "xpack.actions.builtin.serviceNowSIRTitle": "ServiceNow SecOps", - "xpack.actions.builtin.serviceNowTitle": "ServiceNow", - "xpack.actions.builtin.slack.errorPostingErrorMessage": "发布 slack 消息时出错", - "xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage": "发布 slack 消息时出错,稍后重试", - "xpack.actions.builtin.slack.slackConfigurationErrorNoHostname": "配置 slack 操作时出错:无法解析 webhookUrl 中的主机名", - "xpack.actions.builtin.slack.unexpectedNullResponseErrorMessage": "来自 slack 的异常空响应", - "xpack.actions.builtin.slackTitle": "Slack", - "xpack.actions.builtin.swimlaneTitle": "泳道", - "xpack.actions.builtin.teams.errorPostingRetryLaterErrorMessage": "发布 Microsoft Teams 消息时出错,请稍后重试", - "xpack.actions.builtin.teams.invalidResponseErrorMessage": "向 Microsoft Teams 发布时出错,无效响应", - "xpack.actions.builtin.teams.teamsConfigurationErrorNoHostname": "配置 Teams 操作时出错:无法解析 webhookUrl 中的主机名", - "xpack.actions.builtin.teams.unreachableErrorMessage": "向 Microsoft Teams 发布时出错,意外错误", - "xpack.actions.builtin.teamsTitle": "Microsoft Teams", - "xpack.actions.builtin.webhook.invalidResponseErrorMessage": "调用 webhook 时出错,响应无效", - "xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage": "调用 webhook 时出错,请稍后重试", - "xpack.actions.builtin.webhook.invalidUsernamePassword": "必须指定用户及密码", - "xpack.actions.builtin.webhook.requestFailedErrorMessage": "调用 webhook 时出错,请求失败", - "xpack.actions.builtin.webhook.unreachableErrorMessage": "调用时 webhook 出错,非预期错误", - "xpack.actions.builtin.webhookTitle": "Webhook", - "xpack.actions.builtin.xmatters.invalidUsernamePassword": "必须同时指定用户和密码。", - "xpack.actions.builtin.xmatters.missingConfigUrl": "请提供有效的 configUrl", - "xpack.actions.builtin.xmatters.missingPassword": "请提供有效密码", - "xpack.actions.builtin.xmatters.missingSecretsUrl": "请提供具有 API 密钥的有效 secretsUrl", - "xpack.actions.builtin.xmatters.missingUser": "请提供有效用户名", - "xpack.actions.builtin.xmatters.noSecretsProvided": "请提供 secretsUrl 链接或用户/密码以进行身份验证", - "xpack.actions.builtin.xmatters.noUserPassWhenSecretsUrl": "无法将用户/密码用于 URL 身份验证。请提供有效 secretsUrl 或使用基本身份验证。", - "xpack.actions.builtin.xmatters.postingErrorMessage": "触发 xMatters 工作流时出错", - "xpack.actions.builtin.xmatters.shouldNotHaveConfigUrl": "usesBasic 为 false 时不得提供 configUrl", - "xpack.actions.builtin.xmatters.shouldNotHaveSecretsUrl": "usesBasic 为 true 时不得提供 secretsUrl", - "xpack.actions.builtin.xmatters.shouldNotHaveUsernamePassword": "usesBasic 为 false 时不得提供用户名和密码", - "xpack.actions.builtin.xmattersTitle": "xMatters", "xpack.actions.featureRegistry.actionsFeatureName": "操作和连接器", "xpack.actions.savedObjects.goToConnectorsButtonText": "前往连接器", "xpack.actions.serverSideErrors.unavailableLicenseInformationErrorMessage": "操作不可用 - 许可信息当前不可用。", + "xpack.stackConnectors.casesWebhook.configurationError": "配置案例 Webhook 操作时出错:{err}", + "xpack.stackConnectors.casesWebhook.configurationErrorNoHostname": "配置案例 Webhook 操作时出错:无法解析 {url}:{err}", + "xpack.stackConnectors.casesWebhook.configuration.apiAllowedHostsError": "配置连接器操作时出错:{message}", + "xpack.stackConnectors.resilient.configuration.apiAllowedHostsError": "配置连接器操作时出错:{message}", + "xpack.stackConnectors.serviceNow.configuration.apiAllowedHostsError": "配置连接器操作时出错:{message}", + "xpack.stackConnectors.serviceNow.configuration.apiValidateMissingOAuthFieldError": "isOAuth = {isOAuth} 时,必须提供 {field}", + "xpack.stackConnectors.serviceNow.configuration.apiValidateOAuthFieldError": "不得与 isOAuth = {isOAuth} 一起提供 {field}", + "xpack.stackConnectors.email.customViewInKibanaMessage": "此消息由 Kibana 发送。[{kibanaFooterLinkText}]({link})。", + "xpack.stackConnectors.jira.configuration.apiAllowedHostsError": "配置连接器操作时出错:{message}", + "xpack.stackConnectors.pagerduty.invalidTimestampErrorMessage": "解析时间戳“{timestamp}”时出错", + "xpack.stackConnectors.pagerduty.missingDedupkeyErrorMessage": "当 eventAction 是“{eventAction}”时需要 DedupKey", + "xpack.stackConnectors.pagerduty.configurationError": "配置 pagerduty 操作时出错:{message}", + "xpack.stackConnectors.pagerduty.postingRetryErrorMessage": "发布 pagerduty 事件时出错:http 状态 {status},请稍后重试", + "xpack.stackConnectors.pagerduty.postingUnexpectedErrorMessage": "发布 pagerduty 事件时出错:非预期状态 {status}", + "xpack.stackConnectors.pagerduty.timestampParsingFailedErrorMessage": "解析时间戳“{timestamp}”出错:{message}", + "xpack.stackConnectors.slack.errorPostingRetryDateErrorMessage": "发布 Slack 消息时出错,在 {retryString} 重试", + "xpack.stackConnectors.slack.configurationError": "配置 slack 操作时出错:{message}", + "xpack.stackConnectors.slack.unexpectedHttpResponseErrorMessage": "来自 slack 的非预期 http 响应:{httpStatus} {httpStatusText}", + "xpack.stackConnectors.swimlane.configuration.apiAllowedHostsError": "配置连接器操作时出错:{message}", + "xpack.stackConnectors.teams.errorPostingRetryDateErrorMessage": "发布 Microsoft Teams 消息时出错,请在 {retryString} 重试", + "xpack.stackConnectors.teams.configurationError": "配置 Teams 操作时出错:{message}", + "xpack.stackConnectors.webhook.invalidResponseRetryDateErrorMessage": "调用 webhook 时出错,请在 {retryString} 重试", + "xpack.stackConnectors.webhook.configurationError": "配置 Webhook 操作时出错:{message}", + "xpack.stackConnectors.webhook.configurationErrorNoHostname": "配置 Webhook 操作时出错:无法解析 url:{err}", + "xpack.stackConnectors.xmatters.postingRetryErrorMessage": "触发 xMatters 流时出错:http 状态为 {status},请稍后重试", + "xpack.stackConnectors.xmatters.unexpectedStatusErrorMessage": "触发 xMatters 流时出错:非预期状态 {status}", + "xpack.stackConnectors.xmatters.configurationError": "配置 xMatters 操作时出错:{message}", + "xpack.stackConnectors.xmatters.configurationErrorNoHostname": "配置 xMatters 操作时出错:无法解析 url:{err}", + "xpack.stackConnectors.xmatters.hostnameNotAllowed": "{message}", + "xpack.stackConnectors.xmatters.invalidUrlError": "secretsUrl 无效:{err}", + "xpack.stackConnectors.casesWebhook.title": "Webhook - 案例管理", + "xpack.stackConnectors.jira.title": "Jira", + "xpack.stackConnectors.resilient.title": "IBM Resilient", + "xpack.stackConnectors.casesWebhook.invalidUsernamePassword": "必须指定用户及密码", + "xpack.stackConnectors.serviceNow.configuration.apiBasicAuthCredentialsError": "必须同时指定用户名和密码", + "xpack.stackConnectors.serviceNow.configuration.apiCredentialsError": "必须指定基本身份验证或 OAuth 凭据", + "xpack.stackConnectors.serviceNow.configuration.apiOAuthCredentialsError": "必须同时指定 clientSecret 和 privateKey", + "xpack.stackConnectors.email.errorSendingErrorMessage": "发送电子邮件时出错", + "xpack.stackConnectors.email.kibanaFooterLinkText": "前往 Kibana", + "xpack.stackConnectors.email.sentByKibanaMessage": "此消息由 Kibana 发送。", + "xpack.stackConnectors.email.title": "电子邮件", + "xpack.stackConnectors.esIndex.errorIndexingErrorMessage": "索引文档时出错", + "xpack.stackConnectors.esIndex.title": "索引", + "xpack.stackConnectors.pagerduty.postingErrorMessage": "发布 pagerduty 事件时出错", + "xpack.stackConnectors.pagerduty.title": "PagerDuty", + "xpack.stackConnectors.serverLog.errorLoggingErrorMessage": "记录消息时出错", + "xpack.stackConnectors.serverLog.title": "服务器日志", + "xpack.stackConnectors.serviceNowITOM.title": "ServiceNow ITOM", + "xpack.stackConnectors.serviceNowITSM.title": "ServiceNow ITSM", + "xpack.stackConnectors.serviceNowSIR.title": "ServiceNow SecOps", + "xpack.stackConnectors.serviceNow.title": "ServiceNow", + "xpack.stackConnectors.slack.errorPostingErrorMessage": "发布 slack 消息时出错", + "xpack.stackConnectors.slack.errorPostingRetryLaterErrorMessage": "发布 slack 消息时出错,稍后重试", + "xpack.stackConnectors.slack.configurationErrorNoHostname": "配置 slack 操作时出错:无法解析 webhookUrl 中的主机名", + "xpack.stackConnectors.slack.unexpectedNullResponseErrorMessage": "来自 slack 的异常空响应", + "xpack.stackConnectors.slack.title": "Slack", + "xpack.stackConnectors.swimlane.title": "泳道", + "xpack.stackConnectors.teams.errorPostingRetryLaterErrorMessage": "发布 Microsoft Teams 消息时出错,请稍后重试", + "xpack.stackConnectors.teams.invalidResponseErrorMessage": "向 Microsoft Teams 发布时出错,无效响应", + "xpack.stackConnectors.teams.configurationErrorNoHostname": "配置 Teams 操作时出错:无法解析 webhookUrl 中的主机名", + "xpack.stackConnectors.teams.unreachableErrorMessage": "向 Microsoft Teams 发布时出错,意外错误", + "xpack.stackConnectors.teams.title": "Microsoft Teams", + "xpack.stackConnectors.webhook.invalidResponseErrorMessage": "调用 webhook 时出错,响应无效", + "xpack.stackConnectors.webhook.invalidResponseRetryLaterErrorMessage": "调用 webhook 时出错,请稍后重试", + "xpack.stackConnectors.webhook.invalidUsernamePassword": "必须指定用户及密码", + "xpack.stackConnectors.webhook.requestFailedErrorMessage": "调用 webhook 时出错,请求失败", + "xpack.stackConnectors.webhook.unreachableErrorMessage": "调用时 webhook 出错,非预期错误", + "xpack.stackConnectors.webhook.title": "Webhook", + "xpack.stackConnectors.xmatters.invalidUsernamePassword": "必须同时指定用户和密码。", + "xpack.stackConnectors.xmatters.missingConfigUrl": "请提供有效的 configUrl", + "xpack.stackConnectors.xmatters.missingPassword": "请提供有效密码", + "xpack.stackConnectors.xmatters.missingSecretsUrl": "请提供具有 API 密钥的有效 secretsUrl", + "xpack.stackConnectors.xmatters.missingUser": "请提供有效用户名", + "xpack.stackConnectors.xmatters.noSecretsProvided": "请提供 secretsUrl 链接或用户/密码以进行身份验证", + "xpack.stackConnectors.xmatters.noUserPassWhenSecretsUrl": "无法将用户/密码用于 URL 身份验证。请提供有效 secretsUrl 或使用基本身份验证。", + "xpack.stackConnectors.xmatters.postingErrorMessage": "触发 xMatters 工作流时出错", + "xpack.stackConnectors.xmatters.shouldNotHaveConfigUrl": "usesBasic 为 false 时不得提供 configUrl", + "xpack.stackConnectors.xmatters.shouldNotHaveSecretsUrl": "usesBasic 为 true 时不得提供 secretsUrl", + "xpack.stackConnectors.xmatters.shouldNotHaveUsernamePassword": "usesBasic 为 false 时不得提供用户名和密码", + "xpack.stackConnectors.xmatters.title": "xMatters", "xpack.aiops.explainLogRateSpikes.loadingState.identifiedFieldCandidates": "已识别 {fieldCandidatesCount, plural, other {# 个字段候选项}}。", "xpack.aiops.explainLogRateSpikes.loadingState.identifiedFieldValuePairs": "已识别 {fieldValuePairsCount, plural, other {# 个重要的字段/值对}}。", "xpack.aiops.index.dataLoader.internalServerErrorMessage": "加载索引 {index} 中的数据时出错。{message}。请求可能已超时。请尝试使用较小的样例大小或缩小时间范围。", @@ -7224,12 +7198,7 @@ "xpack.apm.serviceIcons.serviceDetails.cloud.projectIdLabel": "项目 ID", "xpack.apm.serviceIcons.serviceDetails.cloud.providerLabel": "云服务提供商", "xpack.apm.serviceIcons.serviceDetails.cloud.serviceNameLabel": "云服务", - "xpack.apm.serviceIcons.serviceDetails.container.containerizedLabel": "容器化", - "xpack.apm.serviceIcons.serviceDetails.container.noLabel": "否", - "xpack.apm.serviceIcons.serviceDetails.container.orchestrationLabel": "编排", - "xpack.apm.serviceIcons.serviceDetails.container.osLabel": "OS", "xpack.apm.serviceIcons.serviceDetails.container.totalNumberInstancesLabel": "实例总数", - "xpack.apm.serviceIcons.serviceDetails.container.yesLabel": "是", "xpack.apm.serviceIcons.serviceDetails.service.agentLabel": "代理名称和版本", "xpack.apm.serviceIcons.serviceDetails.service.frameworkLabel": "框架名称", "xpack.apm.serviceIcons.serviceDetails.service.runtimeLabel": "运行时名称和版本", @@ -9596,7 +9565,6 @@ "xpack.csp.cloudPosturePage.errorRenderer.errorTitle": "我们无法提取您的云安全态势数据", "xpack.csp.cloudPosturePage.loadingDescription": "正在加载……", "xpack.csp.cloudPosturePage.packageNotInstalled.buttonLabel": "添加 CIS 集成", - "xpack.csp.cloudPosturePage.packageNotInstalled.description": "使用我们的 CIS Kubernetes 基准集成根据 CIS 建议衡量 Kubernetes 集群设置。", "xpack.csp.cloudPosturePage.packageNotInstalled.pageTitle": "安装集成以开始", "xpack.csp.cloudPosturePage.packageNotInstalled.solutionNameLabel": "云安全态势", "xpack.csp.cspEvaluationBadge.failLabel": "失败", @@ -13630,10 +13598,8 @@ "xpack.graph.listing.graphsTitle": "图表", "xpack.graph.listing.noDataSource.sampleDataInstallLinkText": "样例数据", "xpack.graph.listing.noItemsMessage": "似乎您没有任何图表。", - "xpack.graph.listing.table.descriptionColumnName": "描述", "xpack.graph.listing.table.entityName": "图表", "xpack.graph.listing.table.entityNamePlural": "图表", - "xpack.graph.listing.table.titleColumnName": "标题", "xpack.graph.missingWorkspaceErrorMessage": "无法使用 ID 加载图表", "xpack.graph.newGraphTitle": "未保存图表", "xpack.graph.noDataSourceNotificationMessageText.managementDataViewLinkText": "“管理”>“数据视图”", @@ -17830,7 +17796,6 @@ "xpack.lens.xyChart.addLayerTooltip": "使用多个图层以组合可视化类型或可视化不同的数据视图。", "xpack.lens.xyChart.addReferenceLineLayerLabel": "参考线", "xpack.lens.xyChart.addReferenceLineLayerLabelDisabledHelp": "添加一些数据以启用参考图层", - "xpack.lens.xyChart.annotation.name": "隐藏标注", "xpack.lens.xyChart.annotationDate": "标注日期", "xpack.lens.xyChart.annotationDate.from": "自", "xpack.lens.xyChart.annotationDate.to": "至", @@ -18464,12 +18429,9 @@ "xpack.maps.map.initializeErrorTitle": "无法初始化地图", "xpack.maps.mapActions.addFeatureError": "无法添加特征到索引。", "xpack.maps.mapActions.removeFeatureError": "无法从索引中移除特征。", - "xpack.maps.mapListing.descriptionFieldTitle": "描述", "xpack.maps.mapListing.entityName": "地图", "xpack.maps.mapListing.entityNamePlural": "地图", "xpack.maps.mapListing.errorAttemptingToLoadSavedMaps": "无法加载地图", - "xpack.maps.mapListing.tableCaption": "Maps", - "xpack.maps.mapListing.titleFieldTitle": "标题", "xpack.maps.mapSavedObjectLabel": "地图", "xpack.maps.mapSettingsPanel.addCustomIcon": "添加定制图标", "xpack.maps.mapSettingsPanel.autoFitToBoundsLocationLabel": "使地图自动适应数据边界", @@ -25584,11 +25546,9 @@ "xpack.securitySolution.administration.os.windows": "Windows", "xpack.securitySolution.alertDetails.enrichmentQueryEndDate": "结束日期", "xpack.securitySolution.alertDetails.enrichmentQueryStartDate": "开始日期", - "xpack.securitySolution.alertDetails.hostRiskClassification": "主机风险分类", "xpack.securitySolution.alertDetails.investigationTimeQueryTitle": "使用威胁情报扩充", "xpack.securitySolution.alertDetails.noEnrichmentsFoundDescription": "我们未找到匹配任何指标匹配规则的威胁情报或此告警的任何扩充。", "xpack.securitySolution.alertDetails.noInvestigationEnrichmentsDescription": "我们未发现字段值具有在过去 30 天中我们默认搜索的威胁情报源提供的其他信息。", - "xpack.securitySolution.alertDetails.noRiskDataDescription": "未找到此告警的主机风险数据", "xpack.securitySolution.alertDetails.overview": "概览", "xpack.securitySolution.alertDetails.overview.enrichedDataTitle": "扩充数据", "xpack.securitySolution.alertDetails.overview.highlightedFields": "突出显示的字段", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/types.ts index 197470eea6cca..36e072cc8db44 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/cases_webhook/types.ts @@ -9,7 +9,7 @@ import type { CasesWebhookPublicConfigurationType, CasesWebhookSecretConfigurationType, ExecutorSubActionPushParams, -} from '@kbn/actions-plugin/server/builtin_action_types/cases_webhook/types'; +} from '@kbn/stack-connectors-plugin/server/connector_types/cases/cases_webhook/types'; import { UserConfiguredActionConnector } from '../../../../types'; export interface CasesWebhookActionParams { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/api.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/api.ts index a799db2702180..913bda49fe53d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/api.ts @@ -6,7 +6,7 @@ */ import { HttpSetup } from '@kbn/core/public'; -import { INTERNAL_BASE_ACTION_API_PATH } from '../../../constants'; +import { INTERNAL_BASE_STACK_CONNECTORS_API_PATH } from '../../../constants'; import { EmailConfig } from '../types'; export async function getServiceConfig({ @@ -16,5 +16,5 @@ export async function getServiceConfig({ http: HttpSetup; service: string; }): Promise>> { - return await http.get(`${INTERNAL_BASE_ACTION_API_PATH}/connector/_email_config/${service}`); + return await http.get(`${INTERNAL_BASE_STACK_CONNECTORS_API_PATH}/_email_config/${service}`); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.tsx index c2a27ab8bae23..213d30ff4e5e4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.tsx @@ -10,7 +10,8 @@ import { isEmpty } from 'lodash'; import { EuiFlexItem, EuiFlexGroup, EuiTitle, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiLink } from '@elastic/eui'; -import { AdditionalEmailServices, InvalidEmailReason } from '@kbn/actions-plugin/common'; +import { InvalidEmailReason } from '@kbn/actions-plugin/common'; +import { AdditionalEmailServices } from '@kbn/stack-connectors-plugin/common'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; import { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.test.ts index f03c869ea5ca4..ee5ba95bf577b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.test.ts @@ -28,7 +28,7 @@ describe('useEmailConfig', () => { const { result } = renderUseEmailConfigHook(); await act(async () => { const res = await result.current.getEmailServiceConfig('gmail'); - expect(http.get).toHaveBeenCalledWith('/internal/actions/connector/_email_config/gmail'); + expect(http.get).toHaveBeenCalledWith('/internal/stack_connectors/_email_config/gmail'); expect(res).toEqual({ host: 'smtp.gmail.com', port: 465, @@ -46,7 +46,7 @@ describe('useEmailConfig', () => { const { result } = renderUseEmailConfigHook(); await act(async () => { const res = await result.current.getEmailServiceConfig('gmail'); - expect(http.get).toHaveBeenCalledWith('/internal/actions/connector/_email_config/gmail'); + expect(http.get).toHaveBeenCalledWith('/internal/stack_connectors/_email_config/gmail'); expect(res).toEqual({ host: 'smtp.gmail.com', port: 465, @@ -61,7 +61,7 @@ describe('useEmailConfig', () => { await act(async () => { const res = await result.current.getEmailServiceConfig('foo'); - expect(http.get).toHaveBeenCalledWith('/internal/actions/connector/_email_config/foo'); + expect(http.get).toHaveBeenCalledWith('/internal/stack_connectors/_email_config/foo'); expect(res).toEqual({ host: '', port: 0, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.ts index c706630f1f6bb..fc0221227783d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.ts @@ -8,7 +8,7 @@ import { isEmpty } from 'lodash'; import { useCallback, useEffect, useRef, useState } from 'react'; import { HttpSetup, IToasts } from '@kbn/core/public'; -import { AdditionalEmailServices } from '@kbn/actions-plugin/common'; +import { AdditionalEmailServices } from '@kbn/stack-connectors-plugin/common'; import { i18n } from '@kbn/i18n'; import { EmailConfig } from '../types'; import { getServiceConfig } from './api'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/types.ts index bb0268795ec0d..85e7be1626b0c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ExecutorSubActionPushParams } from '@kbn/actions-plugin/server/builtin_action_types/jira/types'; +import type { ExecutorSubActionPushParams } from '@kbn/stack-connectors-plugin/server/connector_types/cases/jira/types'; import { UserConfiguredActionConnector } from '../../../../types'; export type JiraActionConnector = UserConfiguredActionConnector; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/types.ts index e845590ce2c3d..12c46d2900213 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ExecutorSubActionPushParams } from '@kbn/actions-plugin/server/builtin_action_types/resilient/types'; +import type { ExecutorSubActionPushParams } from '@kbn/stack-connectors-plugin/server/connector_types/cases/resilient/types'; import { UserConfiguredActionConnector } from '../../../../types'; export type ResilientActionConnector = UserConfiguredActionConnector< diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts index 2fc32972c30d3..95fac75cb9f5b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts @@ -10,8 +10,8 @@ import { HttpSetup } from '@kbn/core/public'; import { ActionTypeExecutorResult, INTERNAL_BASE_ACTION_API_PATH, - snExternalServiceConfig, } from '@kbn/actions-plugin/common'; +import { snExternalServiceConfig } from '@kbn/stack-connectors-plugin/common/servicenow_config'; import { BASE_ACTION_API_PATH } from '../../../constants'; import { API_INFO_ERROR } from './translations'; import { AppInfo, RESTApiError, ServiceNowActionConnector } from './types'; @@ -43,7 +43,7 @@ export async function getChoices({ /** * The app info url should be the same as at: - * x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts + * x-pack/plugins/stack_connectors/server/connector_types/cases/servicenow/service.ts */ const getAppInfoUrl = (url: string, scope: string) => `${url}/api/${scope}/elastic_api/health`; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx index 54cb052c50212..d9e462ae552de 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx @@ -8,7 +8,7 @@ import React, { useCallback, useEffect, useState, useMemo } from 'react'; import { EuiSpacer } from '@elastic/eui'; -import { snExternalServiceConfig } from '@kbn/actions-plugin/common'; +import { snExternalServiceConfig } from '@kbn/stack-connectors-plugin/common/servicenow_config'; import { useFormContext, useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { ActionConnectorFieldsProps } from '../../../../types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/types.ts index 61f66b2be1780..e6fbcf6e81939 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/types.ts @@ -9,7 +9,7 @@ import type { ExecutorSubActionPushParamsITSM, ExecutorSubActionPushParamsSIR, ExecutorSubActionAddEventParams, -} from '@kbn/actions-plugin/server/builtin_action_types/servicenow/types'; +} from '@kbn/stack-connectors-plugin/server/connector_types/cases/servicenow/types'; import { UserConfiguredActionConnector } from '../../../../types'; export type ServiceNowActionConnector = UserConfiguredActionConnector< diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.tsx index 16363cd0b8e43..936e12a564b4a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.tsx @@ -21,7 +21,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { snExternalServiceConfig } from '@kbn/actions-plugin/common'; +import { snExternalServiceConfig } from '@kbn/stack-connectors-plugin/common/servicenow_config'; import { useForm, Form } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { CredentialsApiUrl } from './credentials_api_url'; import { CredentialsAuth, OAuth } from './auth_types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/types.ts index 9c8cf1b852bb9..cf77b094e5a12 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/types.ts @@ -8,7 +8,7 @@ import type { ExecutorSubActionPushParams, MappingConfigType, -} from '@kbn/actions-plugin/server/builtin_action_types/swimlane/types'; +} from '@kbn/stack-connectors-plugin/server/connector_types/cases/swimlane/types'; import { UserConfiguredActionConnector } from '../../../../types'; export type SwimlaneActionConnector = UserConfiguredActionConnector< diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts index 64bd67989cfbf..f23b578d5941c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts @@ -12,6 +12,7 @@ export { INTERNAL_BASE_ALERTING_API_PATH, } from '@kbn/alerting-plugin/common'; export { BASE_ACTION_API_PATH, INTERNAL_BASE_ACTION_API_PATH } from '@kbn/actions-plugin/common'; +export { INTERNAL_BASE_STACK_CONNECTORS_API_PATH } from '@kbn/stack-connectors-plugin/common'; export type Section = 'connectors' | 'rules' | 'alerts' | 'logs'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.test.tsx index c4fb24feac98b..a17efca41fd19 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.test.tsx @@ -123,6 +123,11 @@ describe('AlertsTable', () => { expect(getByTestId('toolbar-updated-at')).not.toBe(null); }); + it('should show alerts count', () => { + const { getByTestId } = render(); + expect(getByTestId('toolbar-alerts-count')).not.toBe(null); + }); + describe('leading control columns', () => { it('should return at least the flyout action control', async () => { const wrapper = render(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/alerts_count/alerts_count.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/alerts_count/alerts_count.tsx new file mode 100644 index 0000000000000..50eb14ccfa3df --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/alerts_count/alerts_count.tsx @@ -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. + */ + +import React, { useMemo } from 'react'; +import { useEuiTheme } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; + +const translateUnit = (totalCount: number) => + i18n.translate('xpack.triggersActionsUI.alertsTable.alertsCountUnit', { + values: { totalCount }, + defaultMessage: `{totalCount, plural, =1 {alert} other {alerts}}`, + }); + +export const AlertsCount = ({ count }: { count: number }) => { + const { euiTheme } = useEuiTheme(); + + const alertCountText = useMemo( + () => `${count.toLocaleString()} ${translateUnit(count)}`, + [count] + ); + + return ( + + {alertCountText} + + ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/index.tsx index 9f9143ffc8863..e261dc7d09cd9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/index.tsx @@ -6,3 +6,4 @@ */ export { getToolbarVisibility } from './toolbar_visibility'; +export { AlertsCount } from './components/alerts_count/alerts_count'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/toolbar_visibility.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/toolbar_visibility.tsx index df79c2723193b..bc4c4817ec37c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/toolbar_visibility.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/toolbar_visibility.tsx @@ -8,18 +8,28 @@ import { EuiDataGridToolBarVisibilityOptions } from '@elastic/eui'; import { EcsFieldsResponse } from '@kbn/rule-registry-plugin/common/search_strategy'; import React, { lazy, Suspense } from 'react'; +import { AlertsCount } from './components/alerts_count/alerts_count'; import { BulkActionsConfig } from '../../../../types'; import { LastUpdatedAt } from './components/last_updated_at'; const BulkActionsToolbar = lazy(() => import('../bulk_actions/components/toolbar')); -const getDefaultVisibility = (updatedAt: number) => { +const getDefaultVisibility = ({ + alertsCount, + updatedAt, +}: { + alertsCount: number; + updatedAt: number; +}) => { + const additionalControls = { + right: , + left: { append: }, + }; + return { + additionalControls, showColumnSelector: true, showSortSelector: true, - additionalControls: { - right: , - }, }; }; @@ -39,7 +49,7 @@ export const getToolbarVisibility = ({ updatedAt: number; }): EuiDataGridToolBarVisibilityOptions => { const selectedRowsCount = rowSelection.size; - const defaultVisibility = getDefaultVisibility(updatedAt); + const defaultVisibility = getDefaultVisibility({ alertsCount, updatedAt }); if (selectedRowsCount === 0 || selectedRowsCount === undefined || bulkActions.length === 0) return defaultVisibility; @@ -51,9 +61,12 @@ export const getToolbarVisibility = ({ ...defaultVisibility.additionalControls, left: { append: ( - - - + <> + + + + + ), }, }, diff --git a/x-pack/plugins/triggers_actions_ui/tsconfig.json b/x-pack/plugins/triggers_actions_ui/tsconfig.json index 8618be6c9c285..991cbc5b01216 100644 --- a/x-pack/plugins/triggers_actions_ui/tsconfig.json +++ b/x-pack/plugins/triggers_actions_ui/tsconfig.json @@ -16,6 +16,7 @@ "references": [ { "path": "../../../src/core/tsconfig.json" }, { "path": "../alerting/tsconfig.json" }, + { "path": "../stack_connectors/tsconfig.json" }, { "path": "../features/tsconfig.json" }, { "path": "../rule_registry/tsconfig.json" }, { "path": "../../../src/plugins/data/tsconfig.json" }, diff --git a/x-pack/test/accessibility/apps/lens.ts b/x-pack/test/accessibility/apps/lens.ts index a48476f5ce5dc..4029cf7787551 100644 --- a/x-pack/test/accessibility/apps/lens.ts +++ b/x-pack/test/accessibility/apps/lens.ts @@ -160,6 +160,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await a11y.testAppSnapshot(); }); + it('lens XY chart with reference line layer', async () => { + await PageObjects.lens.createLayer('referenceLine'); + await a11y.testAppSnapshot(); + }); + + it('lens XY chart with annotations layer', async () => { + await PageObjects.lens.createLayer('annotations'); + await a11y.testAppSnapshot(); + }); + it('saves lens chart', async () => { await PageObjects.lens.save(lensChartName); await a11y.testAppSnapshot(); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts index d77c8858feaf5..7e6aec1347753 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts @@ -502,6 +502,32 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F expect( telemetry.percentile_num_alerts_by_type_per_day.p99['test__cumulative-firing'] ).to.be.greaterThan(0); + + // rules grouped by execution status + expect(telemetry.count_rules_by_execution_status).to.eql({ + success: 15, + error: 3, + warning: 0, + }); + // number of rules that has tags + expect(telemetry.count_rules_with_tags).to.be(21); + // rules grouped by notify when + expect(telemetry.count_rules_by_notify_when).to.eql({ + on_action_group_change: 0, + on_active_alert: 6, + on_throttle_interval: 15, + }); + // rules snoozed + expect(telemetry.count_rules_snoozed).to.be(0); + // rules muted + expect(telemetry.count_rules_muted).to.be(0); + // rules with muted alerts + expect(telemetry.count_rules_with_muted_alerts).to.be(0); + // Connector types grouped by consumers + expect(telemetry.count_connector_types_by_consumers).to.eql({ + // eslint-disable-next-line @typescript-eslint/naming-convention + alertsFixture: { test__noop: 9, test__throw: 9, __slack: 3 }, + }); } it('should retrieve telemetry data in the expected format', async () => { diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts b/x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts index 648902cef8271..833a5522ec37d 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/add_monitor.ts @@ -8,7 +8,8 @@ import uuid from 'uuid'; import { omit } from 'lodash'; import expect from '@kbn/expect'; import { secretKeys } from '@kbn/synthetics-plugin/common/constants/monitor_management'; -import { DataStream, HTTPFields } from '@kbn/synthetics-plugin/common/runtime_types'; +import { ConfigKey, DataStream, HTTPFields } from '@kbn/synthetics-plugin/common/runtime_types'; +import { formatKibanaNamespace } from '@kbn/synthetics-plugin/common/formatters'; import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; import { DEFAULT_FIELDS } from '@kbn/synthetics-plugin/common/constants/monitor_defaults'; import { ALL_SPACES_ID } from '@kbn/security-plugin/common/constants'; @@ -248,6 +249,7 @@ export default function ({ getService }: FtrProviderContext) { const password = `${username}-password`; const SPACE_ID = `test-space-${uuid.v4()}`; const SPACE_NAME = `test-space-name ${uuid.v4()}`; + try { await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); @@ -289,5 +291,145 @@ export default function ({ getService }: FtrProviderContext) { await security.role.delete(roleName); } }); + + it('sets namespace to Kibana space when not set to a custom namespace', async () => { + const username = 'admin'; + const password = `${username}-password`; + const roleName = 'uptime-role'; + const SPACE_ID = `test-space-${uuid.v4()}`; + const SPACE_NAME = `test-space-name ${uuid.v4()}`; + const EXPECTED_NAMESPACE = formatKibanaNamespace(SPACE_ID); + const monitor = { + ...httpMonitorJson, + [ConfigKey.NAMESPACE]: 'default', + }; + let monitorId = ''; + + try { + await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); + await security.role.create(roleName, { + kibana: [ + { + feature: { + uptime: ['all'], + }, + spaces: ['*'], + }, + ], + }); + await security.user.create(username, { + password, + roles: [roleName], + full_name: 'a kibana user', + }); + const apiResponse = await supertestWithoutAuth + .post(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS}`) + .auth(username, password) + .set('kbn-xsrf', 'true') + .send(monitor) + .expect(200); + monitorId = apiResponse.body.id; + expect(apiResponse.body.attributes[ConfigKey.NAMESPACE]).eql(EXPECTED_NAMESPACE); + } finally { + await security.user.delete(username); + await security.role.delete(roleName); + await supertestAPI + .delete(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS}/${monitorId}`) + .set('kbn-xsrf', 'true') + .expect(200); + } + }); + + it('preserves the passed namespace when preserve_namespace is passed', async () => { + const username = 'admin'; + const password = `${username}-password`; + const roleName = 'uptime-role'; + const SPACE_ID = `test-space-${uuid.v4()}`; + const SPACE_NAME = `test-space-name ${uuid.v4()}`; + const monitor = { + ...httpMonitorJson, + [ConfigKey.NAMESPACE]: 'default', + }; + let monitorId = ''; + + try { + await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); + await security.role.create(roleName, { + kibana: [ + { + feature: { + uptime: ['all'], + }, + spaces: ['*'], + }, + ], + }); + await security.user.create(username, { + password, + roles: [roleName], + full_name: 'a kibana user', + }); + const apiResponse = await supertestWithoutAuth + .post(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS}`) + .auth(username, password) + .query({ preserve_namespace: true }) + .set('kbn-xsrf', 'true') + .send(monitor) + .expect(200); + monitorId = apiResponse.body.id; + expect(apiResponse.body.attributes[ConfigKey.NAMESPACE]).eql('default'); + } finally { + await security.user.delete(username); + await security.role.delete(roleName); + await supertestAPI + .delete(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS}/${monitorId}`) + .set('kbn-xsrf', 'true') + .expect(200); + } + }); + + it('sets namespace to custom namespace when set', async () => { + const username = 'admin'; + const password = `${username}-password`; + const roleName = 'uptime-role'; + const SPACE_ID = `test-space-${uuid.v4()}`; + const SPACE_NAME = `test-space-name ${uuid.v4()}`; + const monitor = httpMonitorJson; + let monitorId = ''; + + try { + await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); + await security.role.create(roleName, { + kibana: [ + { + feature: { + uptime: ['all'], + }, + spaces: ['*'], + }, + ], + }); + await security.user.create(username, { + password, + roles: [roleName], + full_name: 'a kibana user', + }); + const apiResponse = await supertestWithoutAuth + .post(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS}`) + .auth(username, password) + .set('kbn-xsrf', 'true') + .send(monitor) + .expect(200); + monitorId = apiResponse.body.id; + expect(apiResponse.body.attributes[ConfigKey.NAMESPACE]).eql(monitor[ConfigKey.NAMESPACE]); + } finally { + await security.user.delete(username); + await security.role.delete(roleName); + await supertestAPI + .delete(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS}/${monitorId}`) + .set('kbn-xsrf', 'true') + .expect(200); + } + }); }); } diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_private_location.ts b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_private_location.ts index ea5ecf8a2c235..826190b73385f 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_private_location.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_private_location.ts @@ -6,8 +6,9 @@ */ import uuid from 'uuid'; import expect from '@kbn/expect'; -import { HTTPFields } from '@kbn/synthetics-plugin/common/runtime_types'; +import { ConfigKey, HTTPFields } from '@kbn/synthetics-plugin/common/runtime_types'; import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; +import { formatKibanaNamespace } from '@kbn/synthetics-plugin/common/formatters'; import { omit } from 'lodash'; import { secretKeys } from '@kbn/synthetics-plugin/common/constants/monitor_management'; import { PackagePolicy } from '@kbn/fleet-plugin/common'; @@ -228,6 +229,7 @@ export default function ({ getService }: FtrProviderContext) { const monitor = { ...httpMonitorJson, name: `Test monitor ${uuid.v4()}`, + [ConfigKey.NAMESPACE]: 'default', locations: [ { id: testFleetPolicyID, @@ -263,7 +265,9 @@ export default function ({ getService }: FtrProviderContext) { .send(monitor) .expect(200); - expect(apiResponse.body.attributes).eql(omit(monitor, secretKeys)); + expect(apiResponse.body.attributes).eql( + omit({ ...monitor, [ConfigKey.NAMESPACE]: formatKibanaNamespace(SPACE_ID) }, secretKeys) + ); monitorId = apiResponse.body.id; const policyResponse = await supertestAPI.get( @@ -277,7 +281,15 @@ export default function ({ getService }: FtrProviderContext) { expect(packagePolicy.policy_id).eql(testFleetPolicyID); expect(packagePolicy.name).eql(`${monitor.name}-Test private location 0-${SPACE_ID}`); - comparePolicies(packagePolicy, getTestSyntheticsPolicy(monitor.name, monitorId)); + comparePolicies( + packagePolicy, + getTestSyntheticsPolicy( + monitor.name, + monitorId, + undefined, + formatKibanaNamespace(SPACE_ID) + ) + ); } finally { await security.user.delete(username); await security.role.delete(roleName); diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts index d4a82030fd989..9dc37ef8f30ec 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts @@ -10,6 +10,7 @@ import expect from '@kbn/expect'; import { format as formatUrl } from 'url'; import { ConfigKey, ProjectMonitorsRequest } from '@kbn/synthetics-plugin/common/runtime_types'; import { API_URLS } from '@kbn/synthetics-plugin/common/constants'; +import { formatKibanaNamespace } from '@kbn/synthetics-plugin/common/formatters'; import { syntheticsMonitorType } from '@kbn/synthetics-plugin/server/legacy_uptime/lib/saved_objects/synthetics_monitor'; import { PackagePolicy } from '@kbn/fleet-plugin/common'; import { FtrProviderContext } from '../../../ftr_provider_context'; @@ -542,7 +543,7 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); const { monitors } = getResponse.body; expect(monitors.length).eql(1); - expect(monitors[0].attributes[ConfigKey.NAMESPACE]).eql(SPACE_ID); + expect(monitors[0].attributes[ConfigKey.NAMESPACE]).eql(formatKibanaNamespace(SPACE_ID)); } finally { await deleteMonitor( projectMonitors.monitors[0].id, diff --git a/x-pack/test/api_integration/apis/uptime/rest/sample_data/test_policy.ts b/x-pack/test/api_integration/apis/uptime/rest/sample_data/test_policy.ts index f24c839ddb296..ed9fa70ab4f59 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/sample_data/test_policy.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/sample_data/test_policy.ts @@ -12,12 +12,13 @@ import { PackagePolicy } from '@kbn/fleet-plugin/common'; export const getTestSyntheticsPolicy = ( name: string, id: string, - locationName?: string + locationName?: string, + namespace?: string ): PackagePolicy => ({ id: '2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default', version: 'WzE2MjYsMV0=', name: 'test-monitor-name-Test private location 0-default', - namespace: 'default', + namespace: namespace || 'testnamespace', package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.10.2' }, enabled: true, policy_id: '5347cd10-0368-11ed-8df7-a7424c6f5167', diff --git a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/archives_metadata.ts b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/archives_metadata.ts index bea2b54d05eeb..ee5d1b20f2e15 100644 --- a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/archives_metadata.ts +++ b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/archives_metadata.ts @@ -10,4 +10,8 @@ export default { start: '2021-08-03T06:50:15.910Z', end: '2021-08-03T07:20:15.910Z', }, + infra_metrics_and_apm: { + start: '2019-06-29T02:48:39.386555Z', + end: '2022-06-29T06:46:26Z', + }, }; diff --git a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/infra_metrics_and_apm/data.json.gz b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/infra_metrics_and_apm/data.json.gz new file mode 100644 index 0000000000000..8d20717e668eb Binary files /dev/null and b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/infra_metrics_and_apm/data.json.gz differ diff --git a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/infra_metrics_and_apm/mappings.json b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/infra_metrics_and_apm/mappings.json new file mode 100644 index 0000000000000..4333590036055 --- /dev/null +++ b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/infra_metrics_and_apm/mappings.json @@ -0,0 +1,17633 @@ +{ + "type": "index", + "value": { + "aliases": {}, + "index": "apm-2019.07.29", + "mappings": { + "_meta": { + "beat": "apm", + "version": "8.0.0" + }, + "date_detection": false, + "dynamic_templates": [ + { + "labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "labels": { + "mapping": { + "type": "boolean" + }, + "match_mapping_type": "boolean", + "path_match": "labels.*" + } + }, + { + "labels": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "labels.*" + } + }, + { + "transaction.marks": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "transaction.marks.*" + } + }, + { + "transaction.marks.*.*": { + "mapping": { + "scaling_factor": 1000000, + "type": "scaled_float" + }, + "path_match": "transaction.marks.*.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "dynamic": "false", + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "client": { + "dynamic": "false", + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "instance": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "container": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "labels": { + "type": "object" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "dynamic": "false", + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "culprit": { + "ignore_above": 1024, + "type": "keyword" + }, + "exception": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "handled": { + "type": "boolean" + }, + "message": { + "norms": false, + "type": "text" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "grouping_key": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "param_message": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "experimental": { + "dynamic": "true", + "type": "object" + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "target_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "golang": { + "properties": { + "goroutines": { + "type": "long" + }, + "heap": { + "properties": { + "allocations": { + "properties": { + "active": { + "type": "long" + }, + "allocated": { + "type": "long" + }, + "frees": { + "type": "long" + }, + "idle": { + "type": "long" + }, + "mallocs": { + "type": "long" + }, + "objects": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "gc": { + "properties": { + "cpu_fraction": { + "type": "float" + }, + "next_gc_limit": { + "type": "long" + }, + "total_count": { + "type": "long" + }, + "total_pause": { + "properties": { + "ns": { + "type": "long" + } + } + } + } + }, + "system": { + "properties": { + "obtained": { + "type": "long" + }, + "released": { + "type": "long" + }, + "stack": { + "type": "long" + }, + "total": { + "type": "long" + } + } + } + } + } + } + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "dynamic": "false", + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "dynamic": "false", + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "finished": { + "type": "boolean" + }, + "headers": { + "enabled": false, + "type": "object" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "jvm": { + "properties": { + "gc": { + "properties": { + "alloc": { + "type": "long" + }, + "count": { + "type": "long" + }, + "time": { + "type": "long" + } + } + }, + "memory": { + "properties": { + "heap": { + "properties": { + "committed": { + "type": "long" + }, + "max": { + "type": "long" + }, + "used": { + "type": "long" + } + } + }, + "non_heap": { + "properties": { + "committed": { + "type": "long" + }, + "max": { + "type": "long" + }, + "used": { + "type": "long" + } + } + } + } + }, + "thread": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "kubernetes": { + "dynamic": "false", + "properties": { + "annotations": { + "type": "object" + }, + "container": { + "properties": { + "image": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "deployment": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "replicaset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "statefulset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "dynamic": "true", + "properties": { + "company": { + "type": "keyword" + }, + "customer_email": { + "type": "keyword" + }, + "customer_name": { + "type": "keyword" + }, + "customer_tier": { + "type": "keyword" + }, + "env": { + "type": "keyword" + }, + "foo": { + "type": "keyword" + }, + "hostname": { + "type": "keyword" + }, + "lorem": { + "type": "keyword" + }, + "multi-line": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "request_id": { + "type": "keyword" + }, + "served_from_cache": { + "type": "keyword" + }, + "this-is-a-very-long-tag-name-without-any-spaces": { + "type": "keyword" + } + } + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "observer": { + "dynamic": "false", + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "listening": { + "ignore_above": 1024, + "type": "keyword" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_major": { + "type": "byte" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "parent": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "dynamic": "false", + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "processor": { + "properties": { + "event": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "ip": { + "type": "ip" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "dynamic": "false", + "properties": { + "environment": { + "ignore_above": 1024, + "type": "keyword" + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "framework": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "language": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "sourcemap": { + "dynamic": "false", + "properties": { + "bundle_filepath": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "span": { + "dynamic": "false", + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "start": { + "properties": { + "us": { + "type": "long" + } + } + }, + "subtype": { + "ignore_above": 1024, + "type": "keyword" + }, + "sync": { + "type": "boolean" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "system": { + "properties": { + "cpu": { + "properties": { + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "memory": { + "properties": { + "actual": { + "properties": { + "free": { + "type": "long" + } + } + }, + "total": { + "type": "long" + } + } + }, + "process": { + "properties": { + "cpu": { + "properties": { + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "memory": { + "properties": { + "rss": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "size": { + "type": "long" + } + } + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "timeseries": { + "properties": { + "instance": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "properties": { + "us": { + "type": "long" + } + } + }, + "trace": { + "dynamic": "false", + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "dynamic": "false", + "properties": { + "breakdown": { + "properties": { + "count": { + "type": "long" + } + } + }, + "duration": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "type": "long" + } + } + }, + "us": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "marks": { + "dynamic": "true", + "properties": { + "*": { + "properties": { + "*": { + "dynamic": "true", + "type": "object" + } + } + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + }, + "sampled": { + "type": "boolean" + }, + "self_time": { + "properties": { + "count": { + "type": "long" + }, + "sum": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "span_count": { + "properties": { + "dropped": { + "type": "long" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "dynamic": "false", + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "dynamic": "false", + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "dynamic": "false", + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "view spans": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "query": { + "default_field": [ + "beat.*", + "type", + "tags", + "meta.*", + "message" + ] + } + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": {}, + "index": "metricbeat-2019.07.29", + "mappings": { + "_meta": { + "beat": "metricbeat", + "version": "8.0.0" + }, + "date_detection": false, + "dynamic_templates": [ + { + "labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "aws.tags.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "aws.tags.*" + } + }, + { + "aws.cloudwatch.metrics.*": { + "mapping": { + "type": "double" + }, + "path_match": "aws.cloudwatch.metrics.*" + } + }, + { + "aws.cloudwatch.dimensions.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "aws.cloudwatch.dimensions.*" + } + }, + { + "coredns.stats.dns.request.duration.ns.bucket.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "coredns.stats.dns.request.duration.ns.bucket.*" + } + }, + { + "coredns.stats.dns.request.size.bytes.bucket.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "coredns.stats.dns.request.size.bytes.bucket.*" + } + }, + { + "coredns.stats.dns.response.size.bytes.bucket.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "coredns.stats.dns.response.size.bytes.bucket.*" + } + }, + { + "docker.cpu.core.*.pct": { + "mapping": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "path_match": "docker.cpu.core.*.pct" + } + }, + { + "docker.cpu.core.*.ticks": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "docker.cpu.core.*.ticks" + } + }, + { + "docker.event.actor.attributes": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.event.actor.attributes.*" + } + }, + { + "docker.image.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.image.labels.*" + } + }, + { + "etcd.disk.wal_fsync_duration.ns.bucket.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "etcd.disk.wal_fsync_duration.ns.bucket.*" + } + }, + { + "etcd.disk.backend_commit_duration.ns.bucket.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "etcd.disk.backend_commit_duration.ns.bucket.*" + } + }, + { + "kubernetes.apiserver.http.request.duration.us.percentile.*": { + "mapping": { + "type": "double" + }, + "match_mapping_type": "double", + "path_match": "kubernetes.apiserver.http.request.duration.us.percentile.*" + } + }, + { + "kubernetes.apiserver.http.request.size.bytes.percentile.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "kubernetes.apiserver.http.request.size.bytes.percentile.*" + } + }, + { + "kubernetes.apiserver.http.response.size.bytes.percentile.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "kubernetes.apiserver.http.response.size.bytes.percentile.*" + } + }, + { + "kubernetes.apiserver.request.latency.bucket.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "kubernetes.apiserver.request.latency.bucket.*" + } + }, + { + "kubernetes.apiserver.request.duration.us.bucket.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "kubernetes.apiserver.request.duration.us.bucket.*" + } + }, + { + "kubernetes.controllermanager.http.request.duration.us.percentile.*": { + "mapping": { + "type": "double" + }, + "match_mapping_type": "double", + "path_match": "kubernetes.controllermanager.http.request.duration.us.percentile.*" + } + }, + { + "kubernetes.controllermanager.http.request.size.bytes.percentile.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "kubernetes.controllermanager.http.request.size.bytes.percentile.*" + } + }, + { + "kubernetes.controllermanager.http.response.size.bytes.percentile.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "kubernetes.controllermanager.http.response.size.bytes.percentile.*" + } + }, + { + "kubernetes.proxy.http.request.duration.us.percentile.*": { + "mapping": { + "type": "double" + }, + "match_mapping_type": "double", + "path_match": "kubernetes.proxy.http.request.duration.us.percentile.*" + } + }, + { + "kubernetes.proxy.http.request.size.bytes.percentile.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "kubernetes.proxy.http.request.size.bytes.percentile.*" + } + }, + { + "kubernetes.proxy.http.response.size.bytes.percentile.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "kubernetes.proxy.http.response.size.bytes.percentile.*" + } + }, + { + "kubernetes.proxy.sync.rules.duration.us.bucket.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "kubernetes.proxy.sync.rules.duration.us.bucket.*" + } + }, + { + "kubernetes.proxy.sync.networkprogramming.duration.us.bucket.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "kubernetes.proxy.sync.networkprogramming.duration.us.bucket.*" + } + }, + { + "kubernetes.scheduler.http.request.duration.us.percentile.*": { + "mapping": { + "type": "double" + }, + "match_mapping_type": "double", + "path_match": "kubernetes.scheduler.http.request.duration.us.percentile.*" + } + }, + { + "kubernetes.scheduler.http.request.size.bytes.percentile.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "kubernetes.scheduler.http.request.size.bytes.percentile.*" + } + }, + { + "kubernetes.scheduler.http.response.size.bytes.percentile.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "kubernetes.scheduler.http.response.size.bytes.percentile.*" + } + }, + { + "kubernetes.scheduler.scheduling.e2e.duration.us.bucket.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "kubernetes.scheduler.scheduling.e2e.duration.us.bucket.*" + } + }, + { + "kubernetes.scheduler.scheduling.duration.seconds.percentile.*": { + "mapping": { + "type": "double" + }, + "match_mapping_type": "double", + "path_match": "kubernetes.scheduler.scheduling.duration.seconds.percentile.*" + } + }, + { + "munin.metrics.*": { + "mapping": { + "type": "double" + }, + "path_match": "munin.metrics.*" + } + }, + { + "prometheus.labels.*": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "prometheus.labels.*" + } + }, + { + "prometheus.metrics.*": { + "mapping": { + "type": "double" + }, + "path_match": "prometheus.metrics.*" + } + }, + { + "system.process.env": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "system.process.env.*" + } + }, + { + "system.process.cgroup.cpuacct.percpu": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "system.process.cgroup.cpuacct.percpu.*" + } + }, + { + "system.raid.disks.states.*": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "system.raid.disks.states.*" + } + }, + { + "traefik.health.response.status_codes.*": { + "mapping": { + "type": "long" + }, + "match_mapping_type": "long", + "path_match": "traefik.health.response.status_codes.*" + } + }, + { + "vsphere.virtualmachine.custom_fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "vsphere.virtualmachine.custom_fields.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "aerospike": { + "properties": { + "namespace": { + "properties": { + "client": { + "properties": { + "delete": { + "properties": { + "error": { + "type": "long" + }, + "not_found": { + "type": "long" + }, + "success": { + "type": "long" + }, + "timeout": { + "type": "long" + } + } + }, + "read": { + "properties": { + "error": { + "type": "long" + }, + "not_found": { + "type": "long" + }, + "success": { + "type": "long" + }, + "timeout": { + "type": "long" + } + } + }, + "write": { + "properties": { + "error": { + "type": "long" + }, + "success": { + "type": "long" + }, + "timeout": { + "type": "long" + } + } + } + } + }, + "device": { + "properties": { + "available": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "free": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "hwm_breached": { + "type": "boolean" + }, + "memory": { + "properties": { + "free": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "used": { + "properties": { + "data": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "index": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "sindex": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "objects": { + "properties": { + "master": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "stop_writes": { + "type": "boolean" + } + } + } + } + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "apache": { + "properties": { + "status": { + "properties": { + "bytes_per_request": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "bytes_per_sec": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "connections": { + "properties": { + "async": { + "properties": { + "closing": { + "type": "long" + }, + "keep_alive": { + "type": "long" + }, + "writing": { + "type": "long" + } + } + }, + "total": { + "type": "long" + } + } + }, + "cpu": { + "properties": { + "children_system": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "children_user": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "load": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "system": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "user": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "load": { + "properties": { + "1": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "15": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "5": { + "scaling_factor": 100, + "type": "scaled_float" + } + } + }, + "requests_per_sec": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "scoreboard": { + "properties": { + "closing_connection": { + "type": "long" + }, + "dns_lookup": { + "type": "long" + }, + "gracefully_finishing": { + "type": "long" + }, + "idle_cleanup": { + "type": "long" + }, + "keepalive": { + "type": "long" + }, + "logging": { + "type": "long" + }, + "open_slot": { + "type": "long" + }, + "reading_request": { + "type": "long" + }, + "sending_reply": { + "type": "long" + }, + "starting_up": { + "type": "long" + }, + "total": { + "type": "long" + }, + "waiting_for_connection": { + "type": "long" + } + } + }, + "total_accesses": { + "type": "long" + }, + "total_kbytes": { + "type": "long" + }, + "uptime": { + "properties": { + "server_uptime": { + "type": "long" + }, + "uptime": { + "type": "long" + } + } + }, + "workers": { + "properties": { + "busy": { + "type": "long" + }, + "idle": { + "type": "long" + } + } + } + } + } + } + }, + "aws": { + "properties": { + "cloudwatch": { + "properties": { + "dimensions": { + "properties": { + "*": { + "type": "object" + } + } + }, + "metrics": { + "properties": { + "*": { + "type": "object" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ec2": { + "properties": { + "cpu": { + "properties": { + "credit_balance": { + "type": "long" + }, + "credit_usage": { + "type": "long" + }, + "surplus_credit_balance": { + "type": "long" + }, + "surplus_credits_charged": { + "type": "long" + }, + "total": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + }, + "diskio": { + "properties": { + "read": { + "properties": { + "bytes": { + "type": "long" + }, + "count": { + "type": "long" + }, + "ops": { + "type": "long" + } + } + }, + "write": { + "properties": { + "bytes": { + "type": "long" + }, + "count": { + "type": "long" + }, + "ops": { + "type": "long" + } + } + } + } + }, + "instance": { + "properties": { + "core": { + "properties": { + "count": { + "type": "long" + } + } + }, + "image": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "monitoring": { + "properties": { + "state": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "private": { + "properties": { + "dns_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + } + } + }, + "public": { + "properties": { + "dns_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + } + } + }, + "state": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "threads_per_core": { + "type": "long" + } + } + }, + "network": { + "properties": { + "in": { + "properties": { + "bytes": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + }, + "out": { + "properties": { + "bytes": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + } + } + }, + "status": { + "properties": { + "check_failed": { + "type": "long" + }, + "check_failed_instance": { + "type": "long" + }, + "check_failed_system": { + "type": "long" + } + } + } + } + }, + "rds": { + "properties": { + "cpu": { + "properties": { + "credit_balance": { + "type": "long" + }, + "credit_usage": { + "type": "long" + }, + "total": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + }, + "database_connections": { + "type": "long" + }, + "db_instance": { + "properties": { + "arn": { + "ignore_above": 1024, + "type": "keyword" + }, + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "identifier": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "deadlocks": { + "type": "long" + }, + "disk_queue_depth": { + "type": "long" + }, + "disk_usage": { + "properties": { + "bin_log": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "replication_slot": { + "properties": { + "mb": { + "type": "long" + } + } + }, + "transaction_logs": { + "properties": { + "mb": { + "type": "long" + } + } + } + } + }, + "failed_sql_server_agent_jobs": { + "type": "long" + }, + "free_local_storage": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "free_storage": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "freeable_memory": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "latency": { + "properties": { + "commit": { + "type": "long" + }, + "ddl": { + "type": "long" + }, + "dml": { + "type": "long" + }, + "insert": { + "type": "long" + }, + "read": { + "type": "long" + }, + "select": { + "type": "long" + }, + "update": { + "type": "long" + }, + "write": { + "type": "long" + } + } + }, + "login_failures": { + "type": "long" + }, + "maximum_used_transaction_ids": { + "type": "long" + }, + "oldest_replication_slot_lag": { + "properties": { + "mb": { + "type": "long" + } + } + }, + "queries": { + "type": "long" + }, + "read_io": { + "properties": { + "ops_per_sec": { + "type": "float" + } + } + }, + "replica_lag": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "swap_usage": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "throughput": { + "properties": { + "commit": { + "type": "long" + }, + "ddl": { + "type": "long" + }, + "delete": { + "type": "long" + }, + "dml": { + "type": "long" + }, + "insert": { + "type": "long" + }, + "network": { + "type": "long" + }, + "network_receive": { + "type": "long" + }, + "network_transmit": { + "type": "long" + }, + "read": { + "type": "long" + }, + "select": { + "type": "long" + }, + "update": { + "type": "long" + }, + "write": { + "type": "long" + } + } + }, + "transaction_logs_generation": { + "type": "long" + }, + "transactions": { + "properties": { + "active": { + "type": "long" + }, + "blocked": { + "type": "long" + } + } + }, + "volume_used": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "write_io": { + "properties": { + "ops_per_sec": { + "type": "float" + } + } + } + } + }, + "s3_daily_storage": { + "properties": { + "bucket": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "number_of_objects": { + "type": "long" + } + } + }, + "s3_request": { + "properties": { + "bucket": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "downloaded": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "errors": { + "properties": { + "4xx": { + "type": "long" + }, + "5xx": { + "type": "long" + } + } + }, + "latency": { + "properties": { + "first_byte": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "total_request": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "requests": { + "properties": { + "delete": { + "type": "long" + }, + "get": { + "type": "long" + }, + "head": { + "type": "long" + }, + "list": { + "type": "long" + }, + "post": { + "type": "long" + }, + "put": { + "type": "long" + }, + "select": { + "type": "long" + }, + "select_returned": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "select_scanned": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "type": "long" + } + } + }, + "uploaded": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "sqs": { + "properties": { + "empty_receives": { + "type": "long" + }, + "messages": { + "properties": { + "delayed": { + "type": "long" + }, + "deleted": { + "type": "long" + }, + "not_visible": { + "type": "long" + }, + "received": { + "type": "long" + }, + "sent": { + "type": "long" + }, + "visible": { + "type": "long" + } + } + }, + "oldest_message_age": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "sent_message_size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "tags": { + "properties": { + "*": { + "type": "object" + }, + "Name": { + "type": "keyword" + } + } + } + } + }, + "beat": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "management": { + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "module": { + "properties": { + "count": { + "type": "long" + } + } + }, + "output": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "queue": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "stats": { + "properties": { + "libbeat": { + "properties": { + "output": { + "properties": { + "events": { + "properties": { + "acked": { + "type": "long" + }, + "active": { + "type": "long" + }, + "batches": { + "type": "long" + }, + "dropped": { + "type": "long" + }, + "duplicates": { + "type": "long" + }, + "failed": { + "type": "long" + }, + "toomany": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "read": { + "properties": { + "bytes": { + "type": "long" + }, + "errors": { + "type": "long" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "write": { + "properties": { + "bytes": { + "type": "long" + }, + "errors": { + "type": "long" + } + } + } + } + } + } + }, + "runtime": { + "properties": { + "goroutines": { + "type": "long" + } + } + }, + "uptime": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ceph": { + "properties": { + "cluster_disk": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "cluster_health": { + "properties": { + "overall_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "timechecks": { + "properties": { + "epoch": { + "type": "long" + }, + "round": { + "properties": { + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "type": "long" + } + } + } + } + } + } + }, + "cluster_status": { + "properties": { + "degraded": { + "properties": { + "objects": { + "type": "long" + }, + "ratio": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "total": { + "type": "long" + } + } + }, + "misplace": { + "properties": { + "objects": { + "type": "long" + }, + "ratio": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "total": { + "type": "long" + } + } + }, + "osd": { + "properties": { + "epoch": { + "type": "long" + }, + "full": { + "type": "boolean" + }, + "nearfull": { + "type": "boolean" + }, + "num_in_osds": { + "type": "long" + }, + "num_osds": { + "type": "long" + }, + "num_remapped_pgs": { + "type": "long" + }, + "num_up_osds": { + "type": "long" + } + } + }, + "pg": { + "properties": { + "avail_bytes": { + "type": "long" + }, + "data_bytes": { + "type": "long" + }, + "total_bytes": { + "type": "long" + }, + "used_bytes": { + "type": "long" + } + } + }, + "pg_state": { + "properties": { + "count": { + "type": "long" + }, + "state_name": { + "type": "long" + }, + "version": { + "type": "long" + } + } + }, + "traffic": { + "properties": { + "read_bytes": { + "type": "long" + }, + "read_op_per_sec": { + "type": "long" + }, + "write_bytes": { + "type": "long" + }, + "write_op_per_sec": { + "type": "long" + } + } + }, + "version": { + "type": "long" + } + } + }, + "monitor_health": { + "properties": { + "available": { + "properties": { + "kb": { + "type": "long" + }, + "pct": { + "type": "long" + } + } + }, + "health": { + "ignore_above": 1024, + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "store_stats": { + "properties": { + "last_updated": { + "type": "long" + }, + "log": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "misc": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "sst": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "kb": { + "type": "long" + } + } + }, + "used": { + "properties": { + "kb": { + "type": "long" + } + } + } + } + }, + "osd_df": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "device_class": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pg_num": { + "type": "long" + }, + "total": { + "properties": { + "byte": { + "type": "long" + } + } + }, + "used": { + "properties": { + "byte": { + "type": "long" + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + }, + "osd_tree": { + "properties": { + "children": { + "ignore_above": 1024, + "type": "keyword" + }, + "crush_weight": { + "type": "float" + }, + "depth": { + "type": "long" + }, + "device_class": { + "ignore_above": 1024, + "type": "keyword" + }, + "exists": { + "type": "boolean" + }, + "father": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "primary_affinity": { + "type": "float" + }, + "reweight": { + "type": "long" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "type_id": { + "type": "long" + } + } + }, + "pool_disk": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "stats": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "objects": { + "type": "long" + }, + "used": { + "properties": { + "bytes": { + "type": "long" + }, + "kb": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "instance": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "cockroachdb": { + "type": "object" + }, + "consul": { + "properties": { + "agent": { + "properties": { + "autopilot": { + "properties": { + "healthy": { + "type": "boolean" + } + } + }, + "runtime": { + "properties": { + "alloc": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "garbage_collector": { + "properties": { + "pause": { + "properties": { + "current": { + "properties": { + "ns": { + "type": "long" + } + } + }, + "total": { + "properties": { + "ns": { + "type": "long" + } + } + } + } + }, + "runs": { + "type": "long" + } + } + }, + "goroutines": { + "type": "long" + }, + "heap_objects": { + "type": "long" + }, + "malloc_count": { + "type": "long" + }, + "sys": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "container": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "properties": { + "annotation_checksum/configmap": { + "type": "keyword" + }, + "annotation_checksum/health": { + "type": "keyword" + }, + "annotation_checksum/secret": { + "type": "keyword" + }, + "annotation_configchecksum": { + "type": "keyword" + }, + "annotation_io_kubernetes_container_hash": { + "type": "keyword" + }, + "annotation_io_kubernetes_container_ports": { + "type": "keyword" + }, + "annotation_io_kubernetes_container_restartCount": { + "type": "keyword" + }, + "annotation_io_kubernetes_container_terminationMessagePath": { + "type": "keyword" + }, + "annotation_io_kubernetes_container_terminationMessagePolicy": { + "type": "keyword" + }, + "annotation_io_kubernetes_pod_terminationGracePeriod": { + "type": "keyword" + }, + "annotation_kubernetes_io/config_hash": { + "type": "keyword" + }, + "annotation_kubernetes_io/config_seen": { + "type": "keyword" + }, + "annotation_kubernetes_io/config_source": { + "type": "keyword" + }, + "annotation_kubernetes_io/limit-ranger": { + "type": "keyword" + }, + "annotation_scheduler_alpha_kubernetes_io/critical-pod": { + "type": "keyword" + }, + "annotation_seccomp_security_alpha_kubernetes_io/pod": { + "type": "keyword" + }, + "app": { + "type": "keyword" + }, + "chart": { + "type": "keyword" + }, + "component": { + "type": "keyword" + }, + "controller-revision-hash": { + "type": "keyword" + }, + "controller-uid": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "heritage": { + "type": "keyword" + }, + "io_kubernetes_container_logpath": { + "type": "keyword" + }, + "io_kubernetes_container_name": { + "type": "keyword" + }, + "io_kubernetes_docker_type": { + "type": "keyword" + }, + "io_kubernetes_pod_name": { + "type": "keyword" + }, + "io_kubernetes_pod_namespace": { + "type": "keyword" + }, + "io_kubernetes_pod_uid": { + "type": "keyword" + }, + "io_kubernetes_sandbox_id": { + "type": "keyword" + }, + "job-name": { + "type": "keyword" + }, + "k8s-app": { + "type": "keyword" + }, + "kubernetes_io/cluster-service": { + "type": "keyword" + }, + "license": { + "type": "keyword" + }, + "maintainer": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "org_label-schema_build-date": { + "type": "keyword" + }, + "org_label-schema_license": { + "type": "keyword" + }, + "org_label-schema_name": { + "type": "keyword" + }, + "org_label-schema_schema-version": { + "type": "keyword" + }, + "org_label-schema_url": { + "type": "keyword" + }, + "org_label-schema_vcs-ref": { + "type": "keyword" + }, + "org_label-schema_vcs-url": { + "type": "keyword" + }, + "org_label-schema_vendor": { + "type": "keyword" + }, + "org_label-schema_version": { + "type": "keyword" + }, + "pod-template-generation": { + "type": "keyword" + }, + "pod-template-hash": { + "type": "keyword" + }, + "release": { + "type": "keyword" + }, + "role": { + "type": "keyword" + }, + "service": { + "type": "keyword" + }, + "statefulset_kubernetes_io/pod-name": { + "type": "keyword" + }, + "tier": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "coredns": { + "properties": { + "stats": { + "properties": { + "dns": { + "properties": { + "cache": { + "properties": { + "hits": { + "properties": { + "count": { + "type": "long" + } + } + }, + "misses": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "request": { + "properties": { + "count": { + "type": "long" + }, + "do": { + "properties": { + "count": { + "type": "long" + } + } + }, + "duration": { + "properties": { + "ns": { + "properties": { + "bucket": { + "properties": { + "*": { + "type": "object" + } + } + }, + "count": { + "type": "long" + }, + "sum": { + "type": "long" + } + } + } + } + }, + "size": { + "properties": { + "bytes": { + "properties": { + "bucket": { + "properties": { + "*": { + "type": "object" + } + } + }, + "count": { + "type": "long" + }, + "sum": { + "type": "long" + } + } + } + } + }, + "type": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "response": { + "properties": { + "rcode": { + "properties": { + "count": { + "type": "long" + } + } + }, + "size": { + "properties": { + "bytes": { + "properties": { + "bucket": { + "properties": { + "*": { + "type": "object" + } + } + }, + "count": { + "type": "long" + }, + "sum": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "panic": { + "properties": { + "count": { + "type": "long" + } + } + }, + "proto": { + "ignore_above": 1024, + "type": "keyword" + }, + "rcode": { + "ignore_above": 1024, + "type": "keyword" + }, + "server": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "couchbase": { + "properties": { + "bucket": { + "properties": { + "data": { + "properties": { + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "disk": { + "properties": { + "fetches": { + "type": "long" + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "item_count": { + "type": "long" + }, + "memory": { + "properties": { + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ops_per_sec": { + "type": "long" + }, + "quota": { + "properties": { + "ram": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "use": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "cluster": { + "properties": { + "hdd": { + "properties": { + "free": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "quota": { + "properties": { + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "by_data": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "value": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "max_bucket_count": { + "type": "long" + }, + "quota": { + "properties": { + "index_memory": { + "properties": { + "mb": { + "type": "long" + } + } + }, + "memory": { + "properties": { + "mb": { + "type": "long" + } + } + } + } + }, + "ram": { + "properties": { + "quota": { + "properties": { + "total": { + "properties": { + "per_node": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "value": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "used": { + "properties": { + "per_node": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "value": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "by_data": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "value": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "node": { + "properties": { + "cmd_get": { + "type": "long" + }, + "couch": { + "properties": { + "docs": { + "properties": { + "data_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "disk_size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "spatial": { + "properties": { + "data_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "disk_size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "views": { + "properties": { + "data_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "disk_size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "cpu_utilization_rate": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "current_items": { + "properties": { + "total": { + "type": "long" + }, + "value": { + "type": "long" + } + } + }, + "ep_bg_fetched": { + "type": "long" + }, + "get_hits": { + "type": "long" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "mcd_memory": { + "properties": { + "allocated": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "reserved": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "free": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "ops": { + "type": "long" + }, + "swap": { + "properties": { + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "uptime": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "vb_replica_curr_items": { + "type": "long" + } + } + } + } + }, + "couchdb": { + "properties": { + "server": { + "properties": { + "couchdb": { + "properties": { + "auth_cache_hits": { + "type": "long" + }, + "auth_cache_misses": { + "type": "long" + }, + "database_reads": { + "type": "long" + }, + "database_writes": { + "type": "long" + }, + "open_databases": { + "type": "long" + }, + "open_os_files": { + "type": "long" + }, + "request_time": { + "type": "long" + } + } + }, + "httpd": { + "properties": { + "bulk_requests": { + "type": "long" + }, + "clients_requesting_changes": { + "type": "long" + }, + "requests": { + "type": "long" + }, + "temporary_view_reads": { + "type": "long" + }, + "view_reads": { + "type": "long" + } + } + }, + "httpd_request_methods": { + "properties": { + "COPY": { + "type": "long" + }, + "DELETE": { + "type": "long" + }, + "GET": { + "type": "long" + }, + "HEAD": { + "type": "long" + }, + "POST": { + "type": "long" + }, + "PUT": { + "type": "long" + } + } + }, + "httpd_status_codes": { + "properties": { + "200": { + "type": "long" + }, + "201": { + "type": "long" + }, + "202": { + "type": "long" + }, + "301": { + "type": "long" + }, + "304": { + "type": "long" + }, + "400": { + "type": "long" + }, + "401": { + "type": "long" + }, + "403": { + "type": "long" + }, + "404": { + "type": "long" + }, + "405": { + "type": "long" + }, + "409": { + "type": "long" + }, + "412": { + "type": "long" + }, + "500": { + "type": "long" + } + } + } + } + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "command": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "ip_addresses": { + "type": "ip" + }, + "labels": { + "properties": { + "annotation_checksum/configmap": { + "type": "keyword" + }, + "annotation_checksum/health": { + "type": "keyword" + }, + "annotation_checksum/secret": { + "type": "keyword" + }, + "annotation_configchecksum": { + "type": "keyword" + }, + "annotation_io_kubernetes_container_hash": { + "type": "keyword" + }, + "annotation_io_kubernetes_container_ports": { + "type": "keyword" + }, + "annotation_io_kubernetes_container_restartCount": { + "type": "keyword" + }, + "annotation_io_kubernetes_container_terminationMessagePath": { + "type": "keyword" + }, + "annotation_io_kubernetes_container_terminationMessagePolicy": { + "type": "keyword" + }, + "annotation_io_kubernetes_pod_terminationGracePeriod": { + "type": "keyword" + }, + "annotation_kubernetes_io/config_hash": { + "type": "keyword" + }, + "annotation_kubernetes_io/config_seen": { + "type": "keyword" + }, + "annotation_kubernetes_io/config_source": { + "type": "keyword" + }, + "annotation_kubernetes_io/limit-ranger": { + "type": "keyword" + }, + "annotation_scheduler_alpha_kubernetes_io/critical-pod": { + "type": "keyword" + }, + "annotation_seccomp_security_alpha_kubernetes_io/pod": { + "type": "keyword" + }, + "app": { + "type": "keyword" + }, + "chart": { + "type": "keyword" + }, + "component": { + "type": "keyword" + }, + "controller-revision-hash": { + "type": "keyword" + }, + "controller-uid": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "heritage": { + "type": "keyword" + }, + "io_kubernetes_container_logpath": { + "type": "keyword" + }, + "io_kubernetes_container_name": { + "type": "keyword" + }, + "io_kubernetes_docker_type": { + "type": "keyword" + }, + "io_kubernetes_pod_name": { + "type": "keyword" + }, + "io_kubernetes_pod_namespace": { + "type": "keyword" + }, + "io_kubernetes_pod_uid": { + "type": "keyword" + }, + "io_kubernetes_sandbox_id": { + "type": "keyword" + }, + "job-name": { + "type": "keyword" + }, + "k8s-app": { + "type": "keyword" + }, + "kubernetes_io/cluster-service": { + "type": "keyword" + }, + "license": { + "type": "keyword" + }, + "maintainer": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "org_label-schema_build-date": { + "type": "keyword" + }, + "org_label-schema_license": { + "type": "keyword" + }, + "org_label-schema_name": { + "type": "keyword" + }, + "org_label-schema_schema-version": { + "type": "keyword" + }, + "org_label-schema_url": { + "type": "keyword" + }, + "org_label-schema_vcs-ref": { + "type": "keyword" + }, + "org_label-schema_vcs-url": { + "type": "keyword" + }, + "org_label-schema_vendor": { + "type": "keyword" + }, + "org_label-schema_version": { + "type": "keyword" + }, + "pod-template-generation": { + "type": "keyword" + }, + "pod-template-hash": { + "type": "keyword" + }, + "release": { + "type": "keyword" + }, + "role": { + "type": "keyword" + }, + "service": { + "type": "keyword" + }, + "statefulset_kubernetes_io/pod-name": { + "type": "keyword" + }, + "tier": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "size": { + "properties": { + "root_fs": { + "type": "long" + }, + "rw": { + "type": "long" + } + } + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "cpu": { + "properties": { + "core": { + "properties": { + "*": { + "properties": { + "pct": { + "type": "object" + }, + "ticks": { + "type": "object" + } + } + }, + "0": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "1": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "2": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "3": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + } + } + }, + "kernel": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "system": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "total": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "user": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + } + } + }, + "diskio": { + "properties": { + "read": { + "properties": { + "bytes": { + "type": "long" + }, + "ops": { + "type": "long" + }, + "rate": { + "type": "long" + } + } + }, + "reads": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "summary": { + "properties": { + "bytes": { + "type": "long" + }, + "ops": { + "type": "long" + }, + "rate": { + "type": "long" + } + } + }, + "total": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "write": { + "properties": { + "bytes": { + "type": "long" + }, + "ops": { + "type": "long" + }, + "rate": { + "type": "long" + } + } + }, + "writes": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "actor": { + "properties": { + "attributes": { + "type": "object" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "from": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "healthcheck": { + "properties": { + "event": { + "properties": { + "end_date": { + "type": "date" + }, + "exit_code": { + "type": "long" + }, + "output": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_date": { + "type": "date" + } + } + }, + "failingstreak": { + "type": "long" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "image": { + "properties": { + "created": { + "type": "date" + }, + "id": { + "properties": { + "current": { + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "size": { + "properties": { + "regular": { + "type": "long" + }, + "virtual": { + "type": "long" + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "info": { + "properties": { + "containers": { + "properties": { + "paused": { + "type": "long" + }, + "running": { + "type": "long" + }, + "stopped": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "images": { + "type": "long" + } + } + }, + "memory": { + "properties": { + "commit": { + "properties": { + "peak": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "fail": { + "properties": { + "count": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "limit": { + "type": "long" + }, + "private_working_set": { + "properties": { + "total": { + "type": "long" + } + } + }, + "rss": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "total": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "max": { + "type": "long" + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "total": { + "type": "long" + } + } + } + } + }, + "network": { + "properties": { + "in": { + "properties": { + "bytes": { + "type": "long" + }, + "dropped": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "errors": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + }, + "inbound": { + "properties": { + "bytes": { + "type": "long" + }, + "dropped": { + "type": "long" + }, + "errors": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + }, + "interface": { + "ignore_above": 1024, + "type": "keyword" + }, + "out": { + "properties": { + "bytes": { + "type": "long" + }, + "dropped": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "errors": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + }, + "outbound": { + "properties": { + "bytes": { + "type": "long" + }, + "dropped": { + "type": "long" + }, + "errors": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + } + } + } + } + }, + "dropwizard": { + "type": "object" + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "ccr": { + "properties": { + "follower": { + "properties": { + "global_checkpoint": { + "type": "long" + }, + "index": { + "ignore_above": 1024, + "type": "keyword" + }, + "operations_written": { + "type": "long" + }, + "shard": { + "properties": { + "number": { + "type": "long" + } + } + }, + "time_since_last_read": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "leader": { + "properties": { + "index": { + "ignore_above": 1024, + "type": "keyword" + }, + "max_seq_no": { + "type": "long" + } + } + } + } + }, + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pending_task": { + "properties": { + "insert_order": { + "type": "long" + }, + "priority": { + "type": "long" + }, + "source": { + "ignore_above": 1024, + "type": "keyword" + }, + "time_in_queue": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "stats": { + "properties": { + "indices": { + "properties": { + "count": { + "type": "long" + }, + "fielddata": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "shards": { + "properties": { + "count": { + "type": "long" + }, + "primaries": { + "type": "long" + } + } + } + } + }, + "nodes": { + "properties": { + "count": { + "type": "long" + }, + "data": { + "type": "long" + }, + "master": { + "type": "long" + } + } + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "index": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "recovery": { + "properties": { + "id": { + "type": "long" + }, + "primary": { + "type": "boolean" + }, + "source": { + "properties": { + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "stage": { + "ignore_above": 1024, + "type": "keyword" + }, + "target": { + "properties": { + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "summary": { + "properties": { + "primaries": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + }, + "deleted": { + "type": "long" + } + } + }, + "segments": { + "properties": { + "count": { + "type": "long" + }, + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + }, + "deleted": { + "type": "long" + } + } + }, + "segments": { + "properties": { + "count": { + "type": "long" + }, + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + }, + "deleted": { + "type": "long" + } + } + }, + "segments": { + "properties": { + "count": { + "type": "long" + }, + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "ml": { + "properties": { + "job": { + "properties": { + "data_counts": { + "properties": { + "invalid_date_count": { + "type": "long" + }, + "processed_record_count": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "jvm": { + "properties": { + "memory": { + "properties": { + "heap": { + "properties": { + "init": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "nonheap": { + "properties": { + "init": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "process": { + "properties": { + "mlockall": { + "type": "boolean" + } + } + }, + "stats": { + "properties": { + "fs": { + "properties": { + "summary": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "free": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "indices": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + }, + "deleted": { + "type": "long" + } + } + }, + "segments": { + "properties": { + "count": { + "type": "long" + }, + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "jvm": { + "properties": { + "gc": { + "properties": { + "collectors": { + "properties": { + "old": { + "properties": { + "collection": { + "properties": { + "count": { + "type": "long" + }, + "ms": { + "type": "long" + } + } + } + } + }, + "young": { + "properties": { + "collection": { + "properties": { + "count": { + "type": "long" + }, + "ms": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "mem": { + "properties": { + "pools": { + "properties": { + "old": { + "properties": { + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak_max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "survivor": { + "properties": { + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak_max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "young": { + "properties": { + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak_max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "shard": { + "properties": { + "number": { + "type": "long" + }, + "primary": { + "type": "boolean" + }, + "relocating_node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "envoyproxy": { + "properties": { + "server": { + "properties": { + "cluster_manager": { + "properties": { + "active_clusters": { + "type": "long" + }, + "cluster_added": { + "type": "long" + }, + "cluster_modified": { + "type": "long" + }, + "cluster_removed": { + "type": "long" + }, + "warming_clusters": { + "type": "long" + } + } + }, + "filesystem": { + "properties": { + "flushed_by_timer": { + "type": "long" + }, + "reopen_failed": { + "type": "long" + }, + "write_buffered": { + "type": "long" + }, + "write_completed": { + "type": "long" + }, + "write_total_buffered": { + "type": "long" + } + } + }, + "http2": { + "properties": { + "header_overflow": { + "type": "long" + }, + "headers_cb_no_stream": { + "type": "long" + }, + "rx_messaging_error": { + "type": "long" + }, + "rx_reset": { + "type": "long" + }, + "too_many_header_frames": { + "type": "long" + }, + "trailers": { + "type": "long" + }, + "tx_reset": { + "type": "long" + } + } + }, + "listener_manager": { + "properties": { + "listener_added": { + "type": "long" + }, + "listener_create_failure": { + "type": "long" + }, + "listener_create_success": { + "type": "long" + }, + "listener_modified": { + "type": "long" + }, + "listener_removed": { + "type": "long" + }, + "total_listeners_active": { + "type": "long" + }, + "total_listeners_draining": { + "type": "long" + }, + "total_listeners_warming": { + "type": "long" + } + } + }, + "runtime": { + "properties": { + "admin_overrides_active": { + "type": "long" + }, + "load_error": { + "type": "long" + }, + "load_success": { + "type": "long" + }, + "num_keys": { + "type": "long" + }, + "override_dir_exists": { + "type": "long" + }, + "override_dir_not_exists": { + "type": "long" + } + } + }, + "server": { + "properties": { + "days_until_first_cert_expiring": { + "type": "long" + }, + "hot_restart_epoch": { + "type": "long" + }, + "live": { + "type": "long" + }, + "memory_allocated": { + "type": "long" + }, + "memory_heap_size": { + "type": "long" + }, + "parent_connections": { + "type": "long" + }, + "total_connections": { + "type": "long" + }, + "uptime": { + "type": "long" + }, + "version": { + "type": "long" + }, + "watchdog_mega_miss": { + "type": "long" + }, + "watchdog_miss": { + "type": "long" + } + } + }, + "stats": { + "properties": { + "overflow": { + "type": "long" + } + } + } + } + } + } + }, + "error": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "etcd": { + "properties": { + "api_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "disk": { + "properties": { + "backend_commit_duration": { + "properties": { + "ns": { + "properties": { + "bucket": { + "properties": { + "*": { + "type": "object" + } + } + }, + "count": { + "type": "long" + }, + "sum": { + "type": "long" + } + } + } + } + }, + "mvcc_db_total_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "wal_fsync_duration": { + "properties": { + "ns": { + "properties": { + "bucket": { + "properties": { + "*": { + "type": "object" + } + } + }, + "count": { + "type": "long" + }, + "sum": { + "type": "long" + } + } + } + } + } + } + }, + "leader": { + "properties": { + "followers": { + "properties": { + "counts": { + "properties": { + "followers": { + "properties": { + "counts": { + "properties": { + "fail": { + "type": "long" + }, + "success": { + "type": "long" + } + } + } + } + } + } + }, + "latency": { + "properties": { + "follower": { + "properties": { + "latency": { + "properties": { + "standardDeviation": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + }, + "followers": { + "properties": { + "latency": { + "properties": { + "average": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "current": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "maximum": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "minimum": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "leader": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "memory": { + "properties": { + "go_memstats_alloc": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "network": { + "properties": { + "client_grpc_received": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "client_grpc_sent": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "self": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "leaderinfo": { + "properties": { + "leader": { + "ignore_above": 1024, + "type": "keyword" + }, + "starttime": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "recv": { + "properties": { + "appendrequest": { + "properties": { + "count": { + "type": "long" + } + } + }, + "bandwidthrate": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "pkgrate": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "send": { + "properties": { + "appendrequest": { + "properties": { + "count": { + "type": "long" + } + } + }, + "bandwidthrate": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "pkgrate": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "starttime": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "grpc_handled": { + "properties": { + "count": { + "type": "long" + } + } + }, + "grpc_started": { + "properties": { + "count": { + "type": "long" + } + } + }, + "has_leader": { + "type": "byte" + }, + "leader_changes": { + "properties": { + "count": { + "type": "long" + } + } + }, + "proposals_committed": { + "properties": { + "count": { + "type": "long" + } + } + }, + "proposals_failed": { + "properties": { + "count": { + "type": "long" + } + } + }, + "proposals_pending": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "store": { + "properties": { + "compareanddelete": { + "properties": { + "fail": { + "type": "long" + }, + "success": { + "type": "long" + } + } + }, + "compareandswap": { + "properties": { + "fail": { + "type": "long" + }, + "success": { + "type": "long" + } + } + }, + "create": { + "properties": { + "fail": { + "type": "long" + }, + "success": { + "type": "long" + } + } + }, + "delete": { + "properties": { + "fail": { + "type": "long" + }, + "success": { + "type": "long" + } + } + }, + "expire": { + "properties": { + "count": { + "type": "long" + } + } + }, + "gets": { + "properties": { + "fail": { + "type": "long" + }, + "success": { + "type": "long" + } + } + }, + "sets": { + "properties": { + "fail": { + "type": "long" + }, + "success": { + "type": "long" + } + } + }, + "update": { + "properties": { + "fail": { + "type": "long" + }, + "success": { + "type": "long" + } + } + }, + "watchers": { + "type": "long" + } + } + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "target_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "golang": { + "properties": { + "expvar": { + "properties": { + "cmdline": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "heap": { + "properties": { + "allocations": { + "properties": { + "active": { + "type": "long" + }, + "allocated": { + "type": "long" + }, + "frees": { + "type": "long" + }, + "idle": { + "type": "long" + }, + "mallocs": { + "type": "long" + }, + "objects": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "cmdline": { + "ignore_above": 1024, + "type": "keyword" + }, + "gc": { + "properties": { + "cpu_fraction": { + "type": "float" + }, + "next_gc_limit": { + "type": "long" + }, + "pause": { + "properties": { + "avg": { + "properties": { + "ns": { + "type": "long" + } + } + }, + "count": { + "type": "long" + }, + "max": { + "properties": { + "ns": { + "type": "long" + } + } + }, + "sum": { + "properties": { + "ns": { + "type": "long" + } + } + } + } + }, + "total_count": { + "type": "long" + }, + "total_pause": { + "properties": { + "ns": { + "type": "long" + } + } + } + } + }, + "system": { + "properties": { + "obtained": { + "type": "long" + }, + "released": { + "type": "long" + }, + "stack": { + "type": "long" + }, + "total": { + "type": "long" + } + } + } + } + } + } + }, + "graphite": { + "properties": { + "server": { + "properties": { + "example": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "haproxy": { + "properties": { + "info": { + "properties": { + "compress": { + "properties": { + "bps": { + "properties": { + "in": { + "type": "long" + }, + "out": { + "type": "long" + }, + "rate_limit": { + "type": "long" + } + } + } + } + }, + "connection": { + "properties": { + "current": { + "type": "long" + }, + "hard_max": { + "type": "long" + }, + "max": { + "type": "long" + }, + "rate": { + "properties": { + "limit": { + "type": "long" + }, + "max": { + "type": "long" + }, + "value": { + "type": "long" + } + } + }, + "ssl": { + "properties": { + "current": { + "type": "long" + }, + "max": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "total": { + "type": "long" + } + } + }, + "idle": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "memory": { + "properties": { + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "pipes": { + "properties": { + "free": { + "type": "long" + }, + "max": { + "type": "long" + }, + "used": { + "type": "long" + } + } + }, + "process_num": { + "type": "long" + }, + "processes": { + "type": "long" + }, + "requests": { + "properties": { + "max": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "run_queue": { + "type": "long" + }, + "session": { + "properties": { + "rate": { + "properties": { + "limit": { + "type": "long" + }, + "max": { + "type": "long" + }, + "value": { + "type": "long" + } + } + } + } + }, + "sockets": { + "properties": { + "max": { + "type": "long" + } + } + }, + "ssl": { + "properties": { + "backend": { + "properties": { + "key_rate": { + "properties": { + "max": { + "type": "long" + }, + "value": { + "type": "long" + } + } + } + } + }, + "cache_misses": { + "type": "long" + }, + "cached_lookups": { + "type": "long" + }, + "frontend": { + "properties": { + "key_rate": { + "properties": { + "max": { + "type": "long" + }, + "value": { + "type": "long" + } + } + }, + "session_reuse": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + }, + "rate": { + "properties": { + "limit": { + "type": "long" + }, + "max": { + "type": "long" + }, + "value": { + "type": "long" + } + } + } + } + }, + "tasks": { + "type": "long" + }, + "ulimit_n": { + "type": "long" + }, + "uptime": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "zlib_mem_usage": { + "properties": { + "max": { + "type": "long" + }, + "value": { + "type": "long" + } + } + } + } + }, + "stat": { + "properties": { + "check": { + "properties": { + "agent": { + "properties": { + "last": { + "type": "long" + } + } + }, + "code": { + "type": "long" + }, + "down": { + "type": "long" + }, + "duration": { + "type": "long" + }, + "failed": { + "type": "long" + }, + "health": { + "properties": { + "fail": { + "type": "long" + }, + "last": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "client": { + "properties": { + "aborted": { + "type": "long" + } + } + }, + "component_type": { + "type": "long" + }, + "compressor": { + "properties": { + "bypassed": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "in": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "out": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "response": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "connection": { + "properties": { + "retried": { + "type": "long" + }, + "time": { + "properties": { + "avg": { + "type": "long" + } + } + }, + "total": { + "type": "long" + } + } + }, + "downtime": { + "type": "long" + }, + "in": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "last_change": { + "type": "long" + }, + "out": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "proxy": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "queue": { + "properties": { + "limit": { + "type": "long" + }, + "time": { + "properties": { + "avg": { + "type": "long" + } + } + } + } + }, + "request": { + "properties": { + "connection": { + "properties": { + "errors": { + "type": "long" + } + } + }, + "denied": { + "type": "long" + }, + "errors": { + "type": "long" + }, + "queued": { + "properties": { + "current": { + "type": "long" + }, + "max": { + "type": "long" + } + } + }, + "rate": { + "properties": { + "max": { + "type": "long" + }, + "value": { + "type": "long" + } + } + }, + "redispatched": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "response": { + "properties": { + "denied": { + "type": "long" + }, + "errors": { + "type": "long" + }, + "http": { + "properties": { + "1xx": { + "type": "long" + }, + "2xx": { + "type": "long" + }, + "3xx": { + "type": "long" + }, + "4xx": { + "type": "long" + }, + "5xx": { + "type": "long" + }, + "other": { + "type": "long" + } + } + }, + "time": { + "properties": { + "avg": { + "type": "long" + } + } + } + } + }, + "selected": { + "properties": { + "total": { + "type": "long" + } + } + }, + "server": { + "properties": { + "aborted": { + "type": "long" + }, + "active": { + "type": "long" + }, + "backup": { + "type": "long" + }, + "id": { + "type": "long" + } + } + }, + "service_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "session": { + "properties": { + "current": { + "type": "long" + }, + "limit": { + "type": "long" + }, + "max": { + "type": "long" + }, + "rate": { + "properties": { + "limit": { + "type": "long" + }, + "max": { + "type": "long" + }, + "value": { + "type": "long" + } + } + } + } + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "throttle": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "tracked": { + "properties": { + "id": { + "type": "long" + } + } + }, + "weight": { + "type": "long" + } + } + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "properties": { + "json": { + "type": "object" + }, + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "headers": { + "type": "object" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "headers": { + "type": "object" + }, + "phrase": { + "ignore_above": 1024, + "type": "keyword" + }, + "status_code": { + "type": "long" + } + } + }, + "server": { + "type": "object" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "jolokia": { + "properties": { + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "secured": { + "type": "boolean" + }, + "server": { + "properties": { + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kafka": { + "properties": { + "broker": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + } + } + }, + "consumergroup": { + "properties": { + "broker": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + } + } + }, + "client": { + "properties": { + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "member_id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "code": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "meta": { + "ignore_above": 1024, + "type": "keyword" + }, + "offset": { + "type": "long" + }, + "partition": { + "type": "long" + }, + "topic": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "partition": { + "properties": { + "broker": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "type": "long" + } + } + }, + "id": { + "type": "long" + }, + "offset": { + "properties": { + "newest": { + "type": "long" + }, + "oldest": { + "type": "long" + } + } + }, + "partition": { + "properties": { + "error": { + "properties": { + "code": { + "type": "long" + } + } + }, + "id": { + "type": "long" + }, + "insync_replica": { + "type": "boolean" + }, + "is_leader": { + "type": "boolean" + }, + "isr": { + "ignore_above": 1024, + "type": "keyword" + }, + "leader": { + "type": "long" + }, + "replica": { + "type": "long" + } + } + }, + "topic": { + "properties": { + "error": { + "properties": { + "code": { + "type": "long" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "topic_broker_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "topic_id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "topic": { + "properties": { + "error": { + "properties": { + "code": { + "type": "long" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "kibana": { + "properties": { + "stats": { + "properties": { + "concurrent_connections": { + "type": "long" + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "index": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "process": { + "properties": { + "event_loop_delay": { + "properties": { + "ms": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "memory": { + "properties": { + "heap": { + "properties": { + "size_limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "uptime": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "request": { + "properties": { + "disconnects": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "response_time": { + "properties": { + "avg": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "max": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "snapshot": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "status": { + "properties": { + "metrics": { + "properties": { + "concurrent_connections": { + "type": "long" + }, + "requests": { + "properties": { + "disconnects": { + "type": "long" + }, + "total": { + "type": "long" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "properties": { + "overall": { + "properties": { + "state": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + } + } + }, + "kubernetes": { + "properties": { + "annotations": { + "type": "object" + }, + "apiserver": { + "properties": { + "audit": { + "properties": { + "event": { + "properties": { + "count": { + "type": "long" + } + } + }, + "rejected": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "client": { + "properties": { + "request": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "etcd": { + "properties": { + "object": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "count": { + "type": "long" + }, + "duration": { + "properties": { + "us": { + "properties": { + "count": { + "type": "long" + }, + "percentile": { + "properties": { + "*": { + "type": "object" + } + } + }, + "sum": { + "type": "double" + } + } + } + } + }, + "size": { + "properties": { + "bytes": { + "properties": { + "count": { + "type": "long" + }, + "percentile": { + "properties": { + "*": { + "type": "object" + } + } + }, + "sum": { + "type": "long" + } + } + } + } + } + } + }, + "response": { + "properties": { + "size": { + "properties": { + "bytes": { + "properties": { + "count": { + "type": "long" + }, + "percentile": { + "properties": { + "*": { + "type": "object" + } + } + }, + "sum": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "process": { + "properties": { + "cpu": { + "properties": { + "sec": { + "type": "double" + } + } + }, + "fds": { + "properties": { + "open": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "resident": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "virtual": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "started": { + "properties": { + "sec": { + "type": "double" + } + } + } + } + }, + "request": { + "properties": { + "client": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "content_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "count": { + "type": "long" + }, + "current": { + "properties": { + "count": { + "type": "long" + } + } + }, + "dry_run": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "properties": { + "bucket": { + "properties": { + "*": { + "type": "object" + } + } + }, + "count": { + "type": "long" + }, + "sum": { + "type": "long" + } + } + } + } + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "handler": { + "ignore_above": 1024, + "type": "keyword" + }, + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "latency": { + "properties": { + "bucket": { + "properties": { + "*": { + "type": "object" + } + } + }, + "count": { + "type": "long" + }, + "sum": { + "type": "long" + } + } + }, + "longrunning": { + "properties": { + "count": { + "type": "long" + } + } + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource": { + "ignore_above": 1024, + "type": "keyword" + }, + "scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "subresource": { + "ignore_above": 1024, + "type": "keyword" + }, + "verb": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "container": { + "properties": { + "_module": { + "properties": { + "labels": { + "properties": { + "app": { + "ignore_above": 1024, + "type": "keyword" + }, + "chart": { + "ignore_above": 1024, + "type": "keyword" + }, + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "controller-revision-hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "controller-uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "heritage": { + "ignore_above": 1024, + "type": "keyword" + }, + "job-name": { + "ignore_above": 1024, + "type": "keyword" + }, + "k8s-app": { + "ignore_above": 1024, + "type": "keyword" + }, + "kubernetes": { + "properties": { + "io/cluster-service": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pod-template-generation": { + "ignore_above": 1024, + "type": "keyword" + }, + "pod-template-hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "release": { + "ignore_above": 1024, + "type": "keyword" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "statefulset": { + "properties": { + "kubernetes": { + "properties": { + "io/pod-name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "tier": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cpu": { + "properties": { + "limit": { + "properties": { + "cores": { + "type": "float" + }, + "nanocores": { + "type": "long" + } + } + }, + "request": { + "properties": { + "cores": { + "type": "float" + }, + "nanocores": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "core": { + "properties": { + "ns": { + "type": "long" + } + } + }, + "limit": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "nanocores": { + "type": "long" + }, + "node": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "ignore_above": 1024, + "type": "keyword" + }, + "logs": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "capacity": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "inodes": { + "properties": { + "count": { + "type": "long" + }, + "free": { + "type": "long" + }, + "used": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "majorpagefaults": { + "type": "long" + }, + "pagefaults": { + "type": "long" + }, + "request": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "rss": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + }, + "limit": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "node": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + }, + "workingset": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "rootfs": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "capacity": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "inodes": { + "properties": { + "used": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "start_time": { + "type": "date" + }, + "status": { + "properties": { + "phase": { + "ignore_above": 1024, + "type": "keyword" + }, + "ready": { + "type": "boolean" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "restarts": { + "type": "long" + } + } + } + } + }, + "controllermanager": { + "properties": { + "client": { + "properties": { + "request": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "handler": { + "ignore_above": 1024, + "type": "keyword" + }, + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "http": { + "properties": { + "request": { + "properties": { + "count": { + "type": "long" + }, + "duration": { + "properties": { + "us": { + "properties": { + "count": { + "type": "long" + }, + "percentile": { + "properties": { + "*": { + "type": "object" + } + } + }, + "sum": { + "type": "double" + } + } + } + } + }, + "size": { + "properties": { + "bytes": { + "properties": { + "count": { + "type": "long" + }, + "percentile": { + "properties": { + "*": { + "type": "object" + } + } + }, + "sum": { + "type": "long" + } + } + } + } + } + } + }, + "response": { + "properties": { + "size": { + "properties": { + "bytes": { + "properties": { + "count": { + "type": "long" + }, + "percentile": { + "properties": { + "*": { + "type": "object" + } + } + }, + "sum": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "leader": { + "properties": { + "is_master": { + "type": "boolean" + } + } + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "collector": { + "properties": { + "count": { + "type": "long" + }, + "eviction": { + "properties": { + "count": { + "type": "long" + } + } + }, + "health": { + "properties": { + "pct": { + "type": "long" + } + } + }, + "unhealthy": { + "properties": { + "count": { + "type": "long" + } + } + } + } + } + } + }, + "process": { + "properties": { + "cpu": { + "properties": { + "sec": { + "type": "double" + } + } + }, + "fds": { + "properties": { + "open": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "resident": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "virtual": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "started": { + "properties": { + "sec": { + "type": "double" + } + } + } + } + }, + "workqueue": { + "properties": { + "adds": { + "properties": { + "count": { + "type": "long" + } + } + }, + "depth": { + "properties": { + "count": { + "type": "long" + } + } + }, + "longestrunning": { + "properties": { + "sec": { + "type": "double" + } + } + }, + "retries": { + "properties": { + "count": { + "type": "long" + } + } + }, + "unfinished": { + "properties": { + "sec": { + "type": "double" + } + } + } + } + }, + "zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "deployment": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "paused": { + "type": "boolean" + }, + "replicas": { + "properties": { + "available": { + "type": "long" + }, + "desired": { + "type": "long" + }, + "unavailable": { + "type": "long" + }, + "updated": { + "type": "long" + } + } + } + } + }, + "event": { + "properties": { + "count": { + "type": "long" + }, + "involved_object": { + "properties": { + "api_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message": { + "copy_to": [ + "message" + ], + "ignore_above": 1024, + "type": "keyword" + }, + "metadata": { + "properties": { + "generate_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "resource_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "self_link": { + "ignore_above": 1024, + "type": "keyword" + }, + "timestamp": { + "properties": { + "created": { + "type": "date" + } + } + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "timestamp": { + "properties": { + "first_occurrence": { + "type": "date" + }, + "last_occurrence": { + "type": "date" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "properties": { + "addonmanager": { + "properties": { + "kubernetes": { + "properties": { + "io/mode": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "app": { + "ignore_above": 1024, + "type": "keyword" + }, + "chart": { + "ignore_above": 1024, + "type": "keyword" + }, + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "controller-revision-hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "controller-uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "heritage": { + "ignore_above": 1024, + "type": "keyword" + }, + "job-name": { + "ignore_above": 1024, + "type": "keyword" + }, + "k8s-app": { + "ignore_above": 1024, + "type": "keyword" + }, + "kubernetes": { + "properties": { + "io/cluster-service": { + "ignore_above": 1024, + "type": "keyword" + }, + "io/name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pod-template-generation": { + "ignore_above": 1024, + "type": "keyword" + }, + "pod-template-hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "release": { + "ignore_above": 1024, + "type": "keyword" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "statefulset": { + "properties": { + "kubernetes": { + "properties": { + "io/pod-name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "statefulset_kubernetes_io/pod-name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tier": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "_module": { + "properties": { + "labels": { + "properties": { + "beta": { + "properties": { + "kubernetes": { + "properties": { + "io/arch": { + "ignore_above": 1024, + "type": "keyword" + }, + "io/fluentd-ds-ready": { + "ignore_above": 1024, + "type": "keyword" + }, + "io/instance-type": { + "ignore_above": 1024, + "type": "keyword" + }, + "io/os": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "google": { + "properties": { + "com/gke-nodepool": { + "ignore_above": 1024, + "type": "keyword" + }, + "com/gke-os-distribution": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "failure-domain": { + "properties": { + "beta": { + "properties": { + "kubernetes": { + "properties": { + "io/region": { + "ignore_above": 1024, + "type": "keyword" + }, + "io/zone": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "kubernetes": { + "properties": { + "io/hostname": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "cpu": { + "properties": { + "allocatable": { + "properties": { + "cores": { + "type": "float" + } + } + }, + "capacity": { + "properties": { + "cores": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "core": { + "properties": { + "ns": { + "type": "long" + } + } + }, + "nanocores": { + "type": "long" + } + } + } + } + }, + "fs": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "capacity": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "inodes": { + "properties": { + "count": { + "type": "long" + }, + "free": { + "type": "long" + }, + "used": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "allocatable": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "capacity": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "majorpagefaults": { + "type": "long" + }, + "pagefaults": { + "type": "long" + }, + "rss": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "workingset": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "network": { + "properties": { + "rx": { + "properties": { + "bytes": { + "type": "long" + }, + "errors": { + "type": "long" + } + } + }, + "tx": { + "properties": { + "bytes": { + "type": "long" + }, + "errors": { + "type": "long" + } + } + } + } + }, + "pod": { + "properties": { + "allocatable": { + "properties": { + "total": { + "type": "long" + } + } + }, + "capacity": { + "properties": { + "total": { + "type": "long" + } + } + } + } + }, + "runtime": { + "properties": { + "imagefs": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "capacity": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "start_time": { + "type": "date" + }, + "status": { + "properties": { + "ready": { + "ignore_above": 1024, + "type": "keyword" + }, + "unschedulable": { + "type": "boolean" + } + } + } + } + }, + "pod": { + "properties": { + "_module": { + "properties": { + "labels": { + "properties": { + "app": { + "ignore_above": 1024, + "type": "keyword" + }, + "chart": { + "ignore_above": 1024, + "type": "keyword" + }, + "component": { + "ignore_above": 1024, + "type": "keyword" + }, + "controller-revision-hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "controller-uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "heritage": { + "ignore_above": 1024, + "type": "keyword" + }, + "job-name": { + "ignore_above": 1024, + "type": "keyword" + }, + "k8s-app": { + "ignore_above": 1024, + "type": "keyword" + }, + "kubernetes": { + "properties": { + "io/cluster-service": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pod-template-generation": { + "ignore_above": 1024, + "type": "keyword" + }, + "pod-template-hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "release": { + "ignore_above": 1024, + "type": "keyword" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "service": { + "ignore_above": 1024, + "type": "keyword" + }, + "statefulset": { + "properties": { + "kubernetes": { + "properties": { + "io/pod-name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "tier": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cpu": { + "properties": { + "usage": { + "properties": { + "limit": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "nanocores": { + "type": "long" + }, + "node": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + } + } + }, + "host_ip": { + "type": "ip" + }, + "ip": { + "type": "ip" + }, + "memory": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "major_page_faults": { + "type": "long" + }, + "page_faults": { + "type": "long" + }, + "rss": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + }, + "limit": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "node": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + }, + "working_set": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "network": { + "properties": { + "rx": { + "properties": { + "bytes": { + "type": "long" + }, + "errors": { + "type": "long" + } + } + }, + "tx": { + "properties": { + "bytes": { + "type": "long" + }, + "errors": { + "type": "long" + } + } + } + } + }, + "start_time": { + "type": "date" + }, + "status": { + "properties": { + "phase": { + "ignore_above": 1024, + "type": "keyword" + }, + "ready": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheduled": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "proxy": { + "properties": { + "client": { + "properties": { + "request": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "handler": { + "ignore_above": 1024, + "type": "keyword" + }, + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "http": { + "properties": { + "request": { + "properties": { + "count": { + "type": "long" + }, + "duration": { + "properties": { + "us": { + "properties": { + "count": { + "type": "long" + }, + "percentile": { + "properties": { + "*": { + "type": "object" + } + } + }, + "sum": { + "type": "double" + } + } + } + } + }, + "size": { + "properties": { + "bytes": { + "properties": { + "count": { + "type": "long" + }, + "percentile": { + "properties": { + "*": { + "type": "object" + } + } + }, + "sum": { + "type": "long" + } + } + } + } + } + } + }, + "response": { + "properties": { + "size": { + "properties": { + "bytes": { + "properties": { + "count": { + "type": "long" + }, + "percentile": { + "properties": { + "*": { + "type": "object" + } + } + }, + "sum": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "process": { + "properties": { + "cpu": { + "properties": { + "sec": { + "type": "double" + } + } + }, + "fds": { + "properties": { + "open": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "resident": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "virtual": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "started": { + "properties": { + "sec": { + "type": "double" + } + } + } + } + }, + "sync": { + "properties": { + "networkprogramming": { + "properties": { + "duration": { + "properties": { + "us": { + "properties": { + "bucket": { + "properties": { + "*": { + "type": "object" + } + } + }, + "count": { + "type": "long" + }, + "sum": { + "type": "long" + } + } + } + } + } + } + }, + "rules": { + "properties": { + "duration": { + "properties": { + "us": { + "properties": { + "bucket": { + "properties": { + "*": { + "type": "object" + } + } + }, + "count": { + "type": "long" + }, + "sum": { + "type": "long" + } + } + } + } + } + } + } + } + } + } + }, + "replicaset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "replicas": { + "properties": { + "available": { + "type": "long" + }, + "desired": { + "type": "long" + }, + "labeled": { + "type": "long" + }, + "observed": { + "type": "long" + }, + "ready": { + "type": "long" + } + } + } + } + }, + "scheduler": { + "properties": { + "client": { + "properties": { + "request": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "handler": { + "ignore_above": 1024, + "type": "keyword" + }, + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "http": { + "properties": { + "request": { + "properties": { + "count": { + "type": "long" + }, + "duration": { + "properties": { + "us": { + "properties": { + "count": { + "type": "long" + }, + "percentile": { + "properties": { + "*": { + "type": "object" + } + } + }, + "sum": { + "type": "double" + } + } + } + } + }, + "size": { + "properties": { + "bytes": { + "properties": { + "count": { + "type": "long" + }, + "percentile": { + "properties": { + "*": { + "type": "object" + } + } + }, + "sum": { + "type": "long" + } + } + } + } + } + } + }, + "response": { + "properties": { + "size": { + "properties": { + "bytes": { + "properties": { + "count": { + "type": "long" + }, + "percentile": { + "properties": { + "*": { + "type": "object" + } + } + }, + "sum": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "leader": { + "properties": { + "is_master": { + "type": "boolean" + } + } + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "operation": { + "ignore_above": 1024, + "type": "keyword" + }, + "process": { + "properties": { + "cpu": { + "properties": { + "sec": { + "type": "double" + } + } + }, + "fds": { + "properties": { + "open": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "resident": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "virtual": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "started": { + "properties": { + "sec": { + "type": "double" + } + } + } + } + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheduling": { + "properties": { + "duration": { + "properties": { + "seconds": { + "properties": { + "count": { + "type": "long" + }, + "percentile": { + "properties": { + "*": { + "type": "object" + } + } + }, + "sum": { + "type": "double" + } + } + } + } + }, + "e2e": { + "properties": { + "duration": { + "properties": { + "us": { + "properties": { + "bucket": { + "properties": { + "*": { + "type": "object" + } + } + }, + "count": { + "type": "long" + }, + "sum": { + "type": "long" + } + } + } + } + } + } + }, + "pod": { + "properties": { + "attempts": { + "properties": { + "count": { + "type": "long" + } + } + }, + "preemption": { + "properties": { + "victims": { + "properties": { + "count": { + "type": "long" + } + } + } + } + } + } + } + } + } + } + }, + "statefulset": { + "properties": { + "created": { + "type": "long" + }, + "generation": { + "properties": { + "desired": { + "type": "long" + }, + "observed": { + "type": "long" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "replicas": { + "properties": { + "desired": { + "type": "long" + }, + "observed": { + "type": "long" + } + } + } + } + }, + "system": { + "properties": { + "_module": { + "properties": { + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "container": { + "ignore_above": 1024, + "type": "keyword" + }, + "cpu": { + "properties": { + "usage": { + "properties": { + "core": { + "properties": { + "ns": { + "type": "long" + } + } + }, + "nanocores": { + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "majorpagefaults": { + "type": "long" + }, + "pagefaults": { + "type": "long" + }, + "rss": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "workingset": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "start_time": { + "type": "date" + } + } + }, + "volume": { + "properties": { + "_module": { + "properties": { + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "fs": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "capacity": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "inodes": { + "properties": { + "count": { + "type": "long" + }, + "free": { + "type": "long" + }, + "used": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "kvm": { + "properties": { + "dommemstat": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "stat": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "type": "long" + } + } + } + } + } + } + }, + "labels": { + "type": "object" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "logstash": { + "properties": { + "node": { + "properties": { + "jvm": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "stats": { + "properties": { + "events": { + "properties": { + "filtered": { + "type": "long" + }, + "in": { + "type": "long" + }, + "out": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "memcached": { + "properties": { + "stats": { + "properties": { + "bytes": { + "properties": { + "current": { + "type": "long" + }, + "limit": { + "type": "long" + } + } + }, + "cmd": { + "properties": { + "get": { + "type": "long" + }, + "set": { + "type": "long" + } + } + }, + "connections": { + "properties": { + "current": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "evictions": { + "type": "long" + }, + "get": { + "properties": { + "hits": { + "type": "long" + }, + "misses": { + "type": "long" + } + } + }, + "items": { + "properties": { + "current": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "pid": { + "type": "long" + }, + "read": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "threads": { + "type": "long" + }, + "uptime": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "written": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "metricset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "mongodb": { + "properties": { + "collstats": { + "properties": { + "collection": { + "ignore_above": 1024, + "type": "keyword" + }, + "commands": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "db": { + "ignore_above": 1024, + "type": "keyword" + }, + "getmore": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "insert": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "lock": { + "properties": { + "read": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "write": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "us": { + "type": "long" + } + } + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "queries": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "remove": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "update": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "us": { + "type": "long" + } + } + } + } + } + } + }, + "dbstats": { + "properties": { + "avg_obj_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "collections": { + "type": "long" + }, + "data_file_version": { + "properties": { + "major": { + "type": "long" + }, + "minor": { + "type": "long" + } + } + }, + "data_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "db": { + "ignore_above": 1024, + "type": "keyword" + }, + "extent_free_list": { + "properties": { + "num": { + "type": "long" + }, + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "file_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "index_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "indexes": { + "type": "long" + }, + "ns_size_mb": { + "properties": { + "mb": { + "type": "long" + } + } + }, + "num_extents": { + "type": "long" + }, + "objects": { + "type": "long" + }, + "storage_size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "metrics": { + "properties": { + "commands": { + "properties": { + "aggregate": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "build_info": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "coll_stats": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "connection_pool_stats": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "count": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "db_stats": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "distinct": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "find": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "get_cmd_line_opts": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "get_last_error": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "get_log": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "get_more": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "get_parameter": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "host_info": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "insert": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "is_master": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "is_self": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "last_collections": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "last_commands": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "list_databased": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "list_indexes": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "ping": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "profile": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "replset_get_rbid": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "replset_get_status": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "replset_heartbeat": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "replset_update_position": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "server_status": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "update": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "whatsmyuri": { + "properties": { + "failed": { + "type": "long" + }, + "total": { + "type": "long" + } + } + } + } + }, + "cursor": { + "properties": { + "open": { + "properties": { + "no_timeout": { + "type": "long" + }, + "pinned": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "timed_out": { + "type": "long" + } + } + }, + "document": { + "properties": { + "deleted": { + "type": "long" + }, + "inserted": { + "type": "long" + }, + "returned": { + "type": "long" + }, + "updated": { + "type": "long" + } + } + }, + "get_last_error": { + "properties": { + "write_timeouts": { + "type": "long" + }, + "write_wait": { + "properties": { + "count": { + "type": "long" + }, + "ms": { + "type": "long" + } + } + } + } + }, + "operation": { + "properties": { + "scan_and_order": { + "type": "long" + }, + "write_conflicts": { + "type": "long" + } + } + }, + "query_executor": { + "properties": { + "scanned_documents": { + "type": "long" + }, + "scanned_indexes": { + "type": "long" + } + } + }, + "replication": { + "properties": { + "apply": { + "properties": { + "attempts_to_become_secondary": { + "type": "long" + }, + "batches": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "ops": { + "type": "long" + } + } + }, + "buffer": { + "properties": { + "count": { + "type": "long" + }, + "max_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "executor": { + "properties": { + "counters": { + "properties": { + "cancels": { + "type": "long" + }, + "event_created": { + "type": "long" + }, + "event_wait": { + "type": "long" + }, + "scheduled": { + "properties": { + "dbwork": { + "type": "long" + }, + "exclusive": { + "type": "long" + }, + "failures": { + "type": "long" + }, + "netcmd": { + "type": "long" + }, + "work": { + "type": "long" + }, + "work_at": { + "type": "long" + } + } + }, + "waits": { + "type": "long" + } + } + }, + "event_waiters": { + "type": "long" + }, + "network_interface": { + "ignore_above": 1024, + "type": "keyword" + }, + "queues": { + "properties": { + "free": { + "type": "long" + }, + "in_progress": { + "properties": { + "dbwork": { + "type": "long" + }, + "exclusive": { + "type": "long" + }, + "network": { + "type": "long" + } + } + }, + "ready": { + "type": "long" + }, + "sleepers": { + "type": "long" + } + } + }, + "shutting_down": { + "type": "boolean" + }, + "unsignaled_events": { + "type": "long" + } + } + }, + "initial_sync": { + "properties": { + "completed": { + "type": "long" + }, + "failed_attempts": { + "type": "long" + }, + "failures": { + "type": "long" + } + } + }, + "network": { + "properties": { + "bytes": { + "type": "long" + }, + "getmores": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "ops": { + "type": "long" + }, + "reders_created": { + "type": "long" + } + } + }, + "preload": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "indexes": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "storage": { + "properties": { + "free_list": { + "properties": { + "search": { + "properties": { + "bucket_exhausted": { + "type": "long" + }, + "requests": { + "type": "long" + }, + "scanned": { + "type": "long" + } + } + } + } + } + } + }, + "ttl": { + "properties": { + "deleted_documents": { + "type": "long" + }, + "passes": { + "type": "long" + } + } + } + } + }, + "replstatus": { + "properties": { + "headroom": { + "properties": { + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + }, + "lag": { + "properties": { + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + }, + "members": { + "properties": { + "arbiter": { + "properties": { + "count": { + "type": "long" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "down": { + "properties": { + "count": { + "type": "long" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "primary": { + "properties": { + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "optime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "recovering": { + "properties": { + "count": { + "type": "long" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rollback": { + "properties": { + "count": { + "type": "long" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "secondary": { + "properties": { + "count": { + "type": "long" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + }, + "optimes": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "startup2": { + "properties": { + "count": { + "type": "long" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "unhealthy": { + "properties": { + "count": { + "type": "long" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "unknown": { + "properties": { + "count": { + "type": "long" + }, + "hosts": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "oplog": { + "properties": { + "first": { + "properties": { + "timestamp": { + "type": "long" + } + } + }, + "last": { + "properties": { + "timestamp": { + "type": "long" + } + } + }, + "size": { + "properties": { + "allocated": { + "type": "long" + }, + "used": { + "type": "long" + } + } + }, + "window": { + "type": "long" + } + } + }, + "optimes": { + "properties": { + "applied": { + "type": "long" + }, + "durable": { + "type": "long" + }, + "last_committed": { + "type": "long" + } + } + }, + "server_date": { + "type": "date" + }, + "set_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "status": { + "properties": { + "asserts": { + "properties": { + "msg": { + "type": "long" + }, + "regular": { + "type": "long" + }, + "rollovers": { + "type": "long" + }, + "user": { + "type": "long" + }, + "warning": { + "type": "long" + } + } + }, + "background_flushing": { + "properties": { + "average": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "flushes": { + "type": "long" + }, + "last": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "last_finished": { + "type": "date" + }, + "total": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "connections": { + "properties": { + "available": { + "type": "long" + }, + "current": { + "type": "long" + }, + "total_created": { + "type": "long" + } + } + }, + "extra_info": { + "properties": { + "heap_usage": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "page_faults": { + "type": "long" + } + } + }, + "global_lock": { + "properties": { + "active_clients": { + "properties": { + "readers": { + "type": "long" + }, + "total": { + "type": "long" + }, + "writers": { + "type": "long" + } + } + }, + "current_queue": { + "properties": { + "readers": { + "type": "long" + }, + "total": { + "type": "long" + }, + "writers": { + "type": "long" + } + } + }, + "total_time": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "journaling": { + "properties": { + "commits": { + "type": "long" + }, + "commits_in_write_lock": { + "type": "long" + }, + "compression": { + "type": "long" + }, + "early_commits": { + "type": "long" + }, + "journaled": { + "properties": { + "mb": { + "type": "long" + } + } + }, + "times": { + "properties": { + "commits": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "commits_in_write_lock": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "dt": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "prep_log_buffer": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "remap_private_view": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "write_to_data_files": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "write_to_journal": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "write_to_data_files": { + "properties": { + "mb": { + "type": "long" + } + } + } + } + }, + "local_time": { + "type": "date" + }, + "locks": { + "properties": { + "collection": { + "properties": { + "acquire": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + }, + "deadlock": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + }, + "wait": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + }, + "us": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + } + } + }, + "database": { + "properties": { + "acquire": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + }, + "deadlock": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + }, + "wait": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + }, + "us": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + } + } + }, + "global": { + "properties": { + "acquire": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + }, + "deadlock": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + }, + "wait": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + }, + "us": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + } + } + }, + "meta_data": { + "properties": { + "acquire": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + }, + "deadlock": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + }, + "wait": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + }, + "us": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + } + } + }, + "oplog": { + "properties": { + "acquire": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + }, + "deadlock": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + }, + "wait": { + "properties": { + "count": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + }, + "us": { + "properties": { + "R": { + "type": "long" + }, + "W": { + "type": "long" + }, + "r": { + "type": "long" + }, + "w": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "memory": { + "properties": { + "bits": { + "type": "long" + }, + "mapped": { + "properties": { + "mb": { + "type": "long" + } + } + }, + "mapped_with_journal": { + "properties": { + "mb": { + "type": "long" + } + } + }, + "resident": { + "properties": { + "mb": { + "type": "long" + } + } + }, + "virtual": { + "properties": { + "mb": { + "type": "long" + } + } + } + } + }, + "network": { + "properties": { + "in": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "out": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "requests": { + "type": "long" + } + } + }, + "ops": { + "properties": { + "counters": { + "properties": { + "command": { + "type": "long" + }, + "delete": { + "type": "long" + }, + "getmore": { + "type": "long" + }, + "insert": { + "type": "long" + }, + "query": { + "type": "long" + }, + "update": { + "type": "long" + } + } + }, + "latencies": { + "properties": { + "commands": { + "properties": { + "count": { + "type": "long" + }, + "latency": { + "type": "long" + } + } + }, + "reads": { + "properties": { + "count": { + "type": "long" + }, + "latency": { + "type": "long" + } + } + }, + "writes": { + "properties": { + "count": { + "type": "long" + }, + "latency": { + "type": "long" + } + } + } + } + }, + "replicated": { + "properties": { + "command": { + "type": "long" + }, + "delete": { + "type": "long" + }, + "getmore": { + "type": "long" + }, + "insert": { + "type": "long" + }, + "query": { + "type": "long" + }, + "update": { + "type": "long" + } + } + } + } + }, + "process": { + "path": "process.name", + "type": "alias" + }, + "storage_engine": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uptime": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "version": { + "path": "service.version", + "type": "alias" + }, + "wired_tiger": { + "properties": { + "cache": { + "properties": { + "dirty": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "maximum": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "pages": { + "properties": { + "evicted": { + "type": "long" + }, + "read": { + "type": "long" + }, + "write": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "concurrent_transactions": { + "properties": { + "read": { + "properties": { + "available": { + "type": "long" + }, + "out": { + "type": "long" + }, + "total_tickets": { + "type": "long" + } + } + }, + "write": { + "properties": { + "available": { + "type": "long" + }, + "out": { + "type": "long" + }, + "total_tickets": { + "type": "long" + } + } + } + } + }, + "log": { + "properties": { + "flushes": { + "type": "long" + }, + "max_file_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "scans": { + "type": "long" + }, + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "syncs": { + "type": "long" + }, + "write": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "writes": { + "type": "long" + } + } + } + } + }, + "write_backs_queued": { + "type": "boolean" + } + } + } + } + }, + "mssql": { + "properties": { + "database": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "performance": { + "properties": { + "active_temp_tables": { + "type": "long" + }, + "batch_requests_per_sec": { + "type": "long" + }, + "buffer": { + "properties": { + "cache_hit": { + "properties": { + "pct": { + "type": "double" + } + } + }, + "checkpoint_pages_per_sec": { + "type": "long" + }, + "database_pages": { + "type": "long" + }, + "page_life_expectancy": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "target_pages": { + "type": "long" + } + } + }, + "compilations_per_sec": { + "type": "long" + }, + "connections_reset_per_sec": { + "type": "long" + }, + "lock_waits_per_sec": { + "type": "long" + }, + "logins_per_sec": { + "type": "long" + }, + "logouts_per_sec": { + "type": "long" + }, + "page_splits_per_sec": { + "type": "long" + }, + "recompilations_per_sec": { + "type": "long" + }, + "transactions": { + "type": "long" + }, + "user_connections": { + "type": "long" + } + } + }, + "transaction_log": { + "properties": { + "space_usage": { + "properties": { + "since_last_backup": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + }, + "pct": { + "type": "float" + } + } + } + } + }, + "stats": { + "properties": { + "active_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "backup_time": { + "type": "date" + }, + "recovery_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "since_last_checkpoint": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total_size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "munin": { + "properties": { + "metrics": { + "properties": { + "*": { + "type": "object" + } + } + }, + "plugin": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "mysql": { + "properties": { + "galera_status": { + "properties": { + "apply": { + "properties": { + "oooe": { + "type": "double" + }, + "oool": { + "type": "double" + }, + "window": { + "type": "double" + } + } + }, + "cert": { + "properties": { + "deps_distance": { + "type": "double" + }, + "index_size": { + "type": "long" + }, + "interval": { + "type": "double" + } + } + }, + "cluster": { + "properties": { + "conf_id": { + "type": "long" + }, + "size": { + "type": "long" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "commit": { + "properties": { + "oooe": { + "type": "double" + }, + "window": { + "type": "long" + } + } + }, + "connected": { + "ignore_above": 1024, + "type": "keyword" + }, + "evs": { + "properties": { + "evict": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "flow_ctl": { + "properties": { + "paused": { + "type": "double" + }, + "paused_ns": { + "type": "long" + }, + "recv": { + "type": "long" + }, + "sent": { + "type": "long" + } + } + }, + "last_committed": { + "type": "long" + }, + "local": { + "properties": { + "bf_aborts": { + "type": "long" + }, + "cert_failures": { + "type": "long" + }, + "commits": { + "type": "long" + }, + "recv": { + "properties": { + "queue": { + "type": "long" + }, + "queue_avg": { + "type": "double" + }, + "queue_max": { + "type": "long" + }, + "queue_min": { + "type": "long" + } + } + }, + "replays": { + "type": "long" + }, + "send": { + "properties": { + "queue": { + "type": "long" + }, + "queue_avg": { + "type": "double" + }, + "queue_max": { + "type": "long" + }, + "queue_min": { + "type": "long" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ready": { + "ignore_above": 1024, + "type": "keyword" + }, + "received": { + "properties": { + "bytes": { + "type": "long" + }, + "count": { + "type": "long" + } + } + }, + "repl": { + "properties": { + "bytes": { + "type": "long" + }, + "count": { + "type": "long" + }, + "data_bytes": { + "type": "long" + }, + "keys": { + "type": "long" + }, + "keys_bytes": { + "type": "long" + }, + "other_bytes": { + "type": "long" + } + } + } + } + }, + "status": { + "properties": { + "aborted": { + "properties": { + "clients": { + "type": "long" + }, + "connects": { + "type": "long" + } + } + }, + "binlog": { + "properties": { + "cache": { + "properties": { + "disk_use": { + "type": "long" + }, + "use": { + "type": "long" + } + } + } + } + }, + "bytes": { + "properties": { + "received": { + "type": "long" + }, + "sent": { + "type": "long" + } + } + }, + "command": { + "properties": { + "delete": { + "type": "long" + }, + "insert": { + "type": "long" + }, + "select": { + "type": "long" + }, + "update": { + "type": "long" + } + } + }, + "connections": { + "type": "long" + }, + "created": { + "properties": { + "tmp": { + "properties": { + "disk_tables": { + "type": "long" + }, + "files": { + "type": "long" + }, + "tables": { + "type": "long" + } + } + } + } + }, + "delayed": { + "properties": { + "errors": { + "type": "long" + }, + "insert_threads": { + "type": "long" + }, + "writes": { + "type": "long" + } + } + }, + "flush_commands": { + "type": "long" + }, + "handler": { + "properties": { + "commit": { + "type": "long" + }, + "delete": { + "type": "long" + }, + "external_lock": { + "type": "long" + }, + "mrr_init": { + "type": "long" + }, + "prepare": { + "type": "long" + }, + "read": { + "properties": { + "first": { + "type": "long" + }, + "key": { + "type": "long" + }, + "last": { + "type": "long" + }, + "next": { + "type": "long" + }, + "prev": { + "type": "long" + }, + "rnd": { + "type": "long" + }, + "rnd_next": { + "type": "long" + } + } + }, + "rollback": { + "type": "long" + }, + "savepoint": { + "type": "long" + }, + "savepoint_rollback": { + "type": "long" + }, + "update": { + "type": "long" + }, + "write": { + "type": "long" + } + } + }, + "innodb": { + "properties": { + "buffer_pool": { + "properties": { + "bytes": { + "properties": { + "data": { + "type": "long" + }, + "dirty": { + "type": "long" + } + } + }, + "dump_status": { + "type": "long" + }, + "load_status": { + "type": "long" + }, + "pages": { + "properties": { + "data": { + "type": "long" + }, + "dirty": { + "type": "long" + }, + "flushed": { + "type": "long" + }, + "free": { + "type": "long" + }, + "latched": { + "type": "long" + }, + "misc": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "pool": { + "properties": { + "reads": { + "type": "long" + }, + "resize_status": { + "type": "long" + }, + "wait_free": { + "type": "long" + } + } + }, + "read": { + "properties": { + "ahead": { + "type": "long" + }, + "ahead_evicted": { + "type": "long" + }, + "ahead_rnd": { + "type": "long" + }, + "requests": { + "type": "long" + } + } + }, + "write_requests": { + "type": "long" + } + } + } + } + }, + "max_used_connections": { + "type": "long" + }, + "open": { + "properties": { + "files": { + "type": "long" + }, + "streams": { + "type": "long" + }, + "tables": { + "type": "long" + } + } + }, + "opened_tables": { + "type": "long" + }, + "queries": { + "type": "long" + }, + "questions": { + "type": "long" + }, + "threads": { + "properties": { + "cached": { + "type": "long" + }, + "connected": { + "type": "long" + }, + "created": { + "type": "long" + }, + "running": { + "type": "long" + } + } + } + } + } + } + }, + "nats": { + "properties": { + "connections": { + "properties": { + "total": { + "type": "long" + } + } + }, + "routes": { + "properties": { + "total": { + "type": "long" + } + } + }, + "server": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "time": { + "type": "date" + } + } + }, + "stats": { + "properties": { + "cores": { + "type": "long" + }, + "cpu": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "http": { + "properties": { + "req_stats": { + "properties": { + "uri": { + "properties": { + "connz": { + "type": "long" + }, + "root": { + "type": "long" + }, + "routez": { + "type": "long" + }, + "subsz": { + "type": "long" + }, + "varz": { + "type": "long" + } + } + } + } + } + } + }, + "in": { + "properties": { + "bytes": { + "type": "long" + }, + "messages": { + "type": "long" + } + } + }, + "mem": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "out": { + "properties": { + "bytes": { + "type": "long" + }, + "messages": { + "type": "long" + } + } + }, + "remotes": { + "type": "long" + }, + "slow_consumers": { + "type": "long" + }, + "total_connections": { + "type": "long" + }, + "uptime": { + "type": "long" + } + } + }, + "subscriptions": { + "properties": { + "cache": { + "properties": { + "fanout": { + "properties": { + "avg": { + "type": "double" + }, + "max": { + "type": "long" + } + } + }, + "hit_rate": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "size": { + "type": "long" + } + } + }, + "inserts": { + "type": "long" + }, + "matches": { + "type": "long" + }, + "removes": { + "type": "long" + }, + "total": { + "type": "long" + } + } + } + } + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "nginx": { + "properties": { + "stubstatus": { + "properties": { + "accepts": { + "type": "long" + }, + "active": { + "type": "long" + }, + "current": { + "type": "long" + }, + "dropped": { + "type": "long" + }, + "handled": { + "type": "long" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "reading": { + "type": "long" + }, + "requests": { + "type": "long" + }, + "waiting": { + "type": "long" + }, + "writing": { + "type": "long" + } + } + } + } + }, + "observer": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "oracle": { + "properties": { + "tablespace": { + "properties": { + "data_file": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "online_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "properties": { + "bytes": { + "type": "long" + }, + "free": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "space": { + "properties": { + "free": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "php_fpm": { + "properties": { + "pool": { + "properties": { + "connections": { + "properties": { + "accepted": { + "type": "long" + }, + "listen_queue_len": { + "type": "long" + }, + "max_listen_queue": { + "type": "long" + }, + "queued": { + "type": "long" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "process_manager": { + "ignore_above": 1024, + "type": "keyword" + }, + "processes": { + "properties": { + "active": { + "type": "long" + }, + "idle": { + "type": "long" + }, + "max_active": { + "type": "long" + }, + "max_children_reached": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "slow_requests": { + "type": "long" + }, + "start_since": { + "type": "long" + }, + "start_time": { + "type": "date" + } + } + }, + "process": { + "properties": { + "last_request_cpu": { + "type": "long" + }, + "last_request_memory": { + "type": "long" + }, + "request_duration": { + "type": "long" + }, + "requests": { + "type": "long" + }, + "script": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_since": { + "type": "long" + }, + "start_time": { + "type": "date" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "postgresql": { + "properties": { + "activity": { + "properties": { + "application_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "backend_start": { + "type": "date" + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + } + } + }, + "database": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "oid": { + "type": "long" + } + } + }, + "pid": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "query_start": { + "type": "date" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_change": { + "type": "date" + }, + "transaction_start": { + "type": "date" + }, + "user": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "waiting": { + "type": "boolean" + } + } + }, + "bgwriter": { + "properties": { + "buffers": { + "properties": { + "allocated": { + "type": "long" + }, + "backend": { + "type": "long" + }, + "backend_fsync": { + "type": "long" + }, + "checkpoints": { + "type": "long" + }, + "clean": { + "type": "long" + }, + "clean_full": { + "type": "long" + } + } + }, + "checkpoints": { + "properties": { + "requested": { + "type": "long" + }, + "scheduled": { + "type": "long" + }, + "times": { + "properties": { + "sync": { + "properties": { + "ms": { + "type": "float" + } + } + }, + "write": { + "properties": { + "ms": { + "type": "float" + } + } + } + } + } + } + }, + "stats_reset": { + "type": "date" + } + } + }, + "database": { + "properties": { + "blocks": { + "properties": { + "hit": { + "type": "long" + }, + "read": { + "type": "long" + }, + "time": { + "properties": { + "read": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "write": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "conflicts": { + "type": "long" + }, + "deadlocks": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "number_of_backends": { + "type": "long" + }, + "oid": { + "type": "long" + }, + "rows": { + "properties": { + "deleted": { + "type": "long" + }, + "fetched": { + "type": "long" + }, + "inserted": { + "type": "long" + }, + "returned": { + "type": "long" + }, + "updated": { + "type": "long" + } + } + }, + "stats_reset": { + "type": "date" + }, + "temporary": { + "properties": { + "bytes": { + "type": "long" + }, + "files": { + "type": "long" + } + } + }, + "transactions": { + "properties": { + "commit": { + "type": "long" + }, + "rollback": { + "type": "long" + } + } + } + } + }, + "statement": { + "properties": { + "database": { + "properties": { + "oid": { + "type": "long" + } + } + }, + "query": { + "properties": { + "calls": { + "type": "long" + }, + "id": { + "type": "long" + }, + "memory": { + "properties": { + "local": { + "properties": { + "dirtied": { + "type": "long" + }, + "hit": { + "type": "long" + }, + "read": { + "type": "long" + }, + "written": { + "type": "long" + } + } + }, + "shared": { + "properties": { + "dirtied": { + "type": "long" + }, + "hit": { + "type": "long" + }, + "read": { + "type": "long" + }, + "written": { + "type": "long" + } + } + }, + "temp": { + "properties": { + "read": { + "type": "long" + }, + "written": { + "type": "long" + } + } + } + } + }, + "rows": { + "type": "long" + }, + "text": { + "ignore_above": 1024, + "type": "keyword" + }, + "time": { + "properties": { + "max": { + "properties": { + "ms": { + "type": "float" + } + } + }, + "mean": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "min": { + "properties": { + "ms": { + "type": "float" + } + } + }, + "stddev": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "total": { + "properties": { + "ms": { + "type": "float" + } + } + } + } + } + } + }, + "user": { + "properties": { + "id": { + "type": "long" + } + } + } + } + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "working_directory": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "prometheus": { + "properties": { + "labels": { + "properties": { + "*": { + "type": "object" + } + } + }, + "metrics": { + "properties": { + "*": { + "type": "object" + } + } + } + } + }, + "rabbitmq": { + "properties": { + "connection": { + "properties": { + "channel_max": { + "type": "long" + }, + "channels": { + "type": "long" + }, + "client_provided": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "frame_max": { + "type": "long" + }, + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "octet_count": { + "properties": { + "received": { + "type": "long" + }, + "sent": { + "type": "long" + } + } + }, + "packet_count": { + "properties": { + "pending": { + "type": "long" + }, + "received": { + "type": "long" + }, + "sent": { + "type": "long" + } + } + }, + "peer": { + "properties": { + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + } + } + }, + "port": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "exchange": { + "properties": { + "auto_delete": { + "type": "boolean" + }, + "durable": { + "type": "boolean" + }, + "internal": { + "type": "boolean" + }, + "messages": { + "properties": { + "publish_in": { + "properties": { + "count": { + "type": "long" + }, + "details": { + "properties": { + "rate": { + "type": "float" + } + } + } + } + }, + "publish_out": { + "properties": { + "count": { + "type": "long" + }, + "details": { + "properties": { + "rate": { + "type": "float" + } + } + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "node": { + "properties": { + "disk": { + "properties": { + "free": { + "properties": { + "bytes": { + "type": "long" + }, + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "fd": { + "properties": { + "total": { + "type": "long" + }, + "used": { + "type": "long" + } + } + }, + "gc": { + "properties": { + "num": { + "properties": { + "count": { + "type": "long" + } + } + }, + "reclaimed": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "io": { + "properties": { + "file_handle": { + "properties": { + "open_attempt": { + "properties": { + "avg": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "count": { + "type": "long" + } + } + } + } + }, + "read": { + "properties": { + "avg": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "bytes": { + "type": "long" + }, + "count": { + "type": "long" + } + } + }, + "reopen": { + "properties": { + "count": { + "type": "long" + } + } + }, + "seek": { + "properties": { + "avg": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "count": { + "type": "long" + } + } + }, + "sync": { + "properties": { + "avg": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "count": { + "type": "long" + } + } + }, + "write": { + "properties": { + "avg": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "bytes": { + "type": "long" + }, + "count": { + "type": "long" + } + } + } + } + }, + "mem": { + "properties": { + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "mnesia": { + "properties": { + "disk": { + "properties": { + "tx": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "ram": { + "properties": { + "tx": { + "properties": { + "count": { + "type": "long" + } + } + } + } + } + } + }, + "msg": { + "properties": { + "store_read": { + "properties": { + "count": { + "type": "long" + } + } + }, + "store_write": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "proc": { + "properties": { + "total": { + "type": "long" + }, + "used": { + "type": "long" + } + } + }, + "processors": { + "type": "long" + }, + "queue": { + "properties": { + "index": { + "properties": { + "journal_write": { + "properties": { + "count": { + "type": "long" + } + } + }, + "read": { + "properties": { + "count": { + "type": "long" + } + } + }, + "write": { + "properties": { + "count": { + "type": "long" + } + } + } + } + } + } + }, + "run": { + "properties": { + "queue": { + "type": "long" + } + } + }, + "socket": { + "properties": { + "total": { + "type": "long" + }, + "used": { + "type": "long" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "arguments": { + "properties": { + "max_priority": { + "type": "long" + } + } + }, + "auto_delete": { + "type": "boolean" + }, + "consumers": { + "properties": { + "count": { + "type": "long" + }, + "utilisation": { + "properties": { + "pct": { + "type": "long" + } + } + } + } + }, + "disk": { + "properties": { + "reads": { + "properties": { + "count": { + "type": "long" + } + } + }, + "writes": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "durable": { + "type": "boolean" + }, + "exclusive": { + "type": "boolean" + }, + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "messages": { + "properties": { + "persistent": { + "properties": { + "count": { + "type": "long" + } + } + }, + "ready": { + "properties": { + "count": { + "type": "long" + }, + "details": { + "properties": { + "rate": { + "type": "float" + } + } + } + } + }, + "total": { + "properties": { + "count": { + "type": "long" + }, + "details": { + "properties": { + "rate": { + "type": "float" + } + } + } + } + }, + "unacknowledged": { + "properties": { + "count": { + "type": "long" + }, + "details": { + "properties": { + "rate": { + "type": "float" + } + } + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vhost": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "redis": { + "properties": { + "info": { + "properties": { + "clients": { + "properties": { + "biggest_input_buf": { + "type": "long" + }, + "blocked": { + "type": "long" + }, + "connected": { + "type": "long" + }, + "longest_output_list": { + "type": "long" + }, + "max_input_buffer": { + "type": "long" + }, + "max_output_buffer": { + "type": "long" + } + } + }, + "cluster": { + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "cpu": { + "properties": { + "used": { + "properties": { + "sys": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "sys_children": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "user": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "user_children": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + }, + "memory": { + "properties": { + "active_defrag": { + "properties": { + "is_running": { + "type": "boolean" + } + } + }, + "allocator": { + "ignore_above": 1024, + "type": "keyword" + }, + "allocator_stats": { + "properties": { + "active": { + "type": "long" + }, + "allocated": { + "type": "long" + }, + "fragmentation": { + "properties": { + "bytes": { + "type": "long" + }, + "ratio": { + "type": "float" + } + } + }, + "resident": { + "type": "long" + }, + "rss": { + "properties": { + "bytes": { + "type": "long" + }, + "ratio": { + "type": "float" + } + } + } + } + }, + "fragmentation": { + "properties": { + "bytes": { + "type": "long" + }, + "ratio": { + "type": "float" + } + } + }, + "max": { + "properties": { + "policy": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "type": "long" + } + } + }, + "used": { + "properties": { + "dataset": { + "type": "long" + }, + "lua": { + "type": "long" + }, + "peak": { + "type": "long" + }, + "rss": { + "type": "long" + }, + "value": { + "type": "long" + } + } + } + } + }, + "persistence": { + "properties": { + "aof": { + "properties": { + "bgrewrite": { + "properties": { + "last_status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "buffer": { + "properties": { + "size": { + "type": "long" + } + } + }, + "copy_on_write": { + "properties": { + "last_size": { + "type": "long" + } + } + }, + "enabled": { + "type": "boolean" + }, + "fsync": { + "properties": { + "delayed": { + "type": "long" + }, + "pending": { + "type": "long" + } + } + }, + "rewrite": { + "properties": { + "buffer": { + "properties": { + "size": { + "type": "long" + } + } + }, + "current_time": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "in_progress": { + "type": "boolean" + }, + "last_time": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "scheduled": { + "type": "boolean" + } + } + }, + "size": { + "properties": { + "base": { + "type": "long" + }, + "current": { + "type": "long" + } + } + }, + "write": { + "properties": { + "last_status": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "loading": { + "type": "boolean" + }, + "rdb": { + "properties": { + "bgsave": { + "properties": { + "current_time": { + "properties": { + "sec": { + "type": "long" + } + } + }, + "in_progress": { + "type": "boolean" + }, + "last_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "last_time": { + "properties": { + "sec": { + "type": "long" + } + } + } + } + }, + "copy_on_write": { + "properties": { + "last_size": { + "type": "long" + } + } + }, + "last_save": { + "properties": { + "changes_since": { + "type": "long" + }, + "time": { + "type": "long" + } + } + } + } + } + } + }, + "replication": { + "properties": { + "backlog": { + "properties": { + "active": { + "type": "long" + }, + "first_byte_offset": { + "type": "long" + }, + "histlen": { + "type": "long" + }, + "size": { + "type": "long" + } + } + }, + "connected_slaves": { + "type": "long" + }, + "master": { + "properties": { + "last_io_seconds_ago": { + "type": "long" + }, + "link_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "offset": { + "type": "long" + }, + "second_offset": { + "type": "long" + }, + "sync": { + "properties": { + "in_progress": { + "type": "boolean" + }, + "last_io_seconds_ago": { + "type": "long" + }, + "left_bytes": { + "type": "long" + } + } + } + } + }, + "master_offset": { + "type": "long" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "slave": { + "properties": { + "is_readonly": { + "type": "boolean" + }, + "offset": { + "type": "long" + }, + "priority": { + "type": "long" + } + } + } + } + }, + "server": { + "properties": { + "arch_bits": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "config_file": { + "ignore_above": 1024, + "type": "keyword" + }, + "gcc_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "git_dirty": { + "ignore_above": 1024, + "type": "keyword" + }, + "git_sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "hz": { + "type": "long" + }, + "lru_clock": { + "type": "long" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "multiplexing_api": { + "ignore_above": 1024, + "type": "keyword" + }, + "run_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "tcp_port": { + "type": "long" + }, + "uptime": { + "type": "long" + } + } + }, + "slowlog": { + "properties": { + "count": { + "type": "long" + } + } + }, + "stats": { + "properties": { + "active_defrag": { + "properties": { + "hits": { + "type": "long" + }, + "key_hits": { + "type": "long" + }, + "key_misses": { + "type": "long" + }, + "misses": { + "type": "long" + } + } + }, + "commands_processed": { + "type": "long" + }, + "connections": { + "properties": { + "received": { + "type": "long" + }, + "rejected": { + "type": "long" + } + } + }, + "instantaneous": { + "properties": { + "input_kbps": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ops_per_sec": { + "type": "long" + }, + "output_kbps": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "keys": { + "properties": { + "evicted": { + "type": "long" + }, + "expired": { + "type": "long" + } + } + }, + "keyspace": { + "properties": { + "hits": { + "type": "long" + }, + "misses": { + "type": "long" + } + } + }, + "latest_fork_usec": { + "type": "long" + }, + "migrate_cached_sockets": { + "type": "long" + }, + "net": { + "properties": { + "input": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "output": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "pubsub": { + "properties": { + "channels": { + "type": "long" + }, + "patterns": { + "type": "long" + } + } + }, + "slave_expires_tracked_keys": { + "type": "long" + }, + "sync": { + "properties": { + "full": { + "type": "long" + }, + "partial": { + "properties": { + "err": { + "type": "long" + }, + "ok": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "key": { + "properties": { + "expire": { + "properties": { + "ttl": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "length": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "keyspace": { + "properties": { + "avg_ttl": { + "type": "long" + }, + "expires": { + "type": "long" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "keys": { + "type": "long" + } + } + } + } + }, + "related": { + "properties": { + "ip": { + "type": "ip" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "system": { + "properties": { + "core": { + "properties": { + "id": { + "type": "long" + }, + "idle": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "iowait": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "irq": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "nice": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "softirq": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "steal": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "system": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "user": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + } + } + }, + "cpu": { + "properties": { + "cores": { + "type": "long" + }, + "idle": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "iowait": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "irq": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "nice": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "softirq": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "steal": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "system": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + }, + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "user": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + } + } + } + } + }, + "diskio": { + "properties": { + "io": { + "properties": { + "time": { + "type": "long" + } + } + }, + "iostat": { + "properties": { + "await": { + "type": "float" + }, + "busy": { + "type": "float" + }, + "queue": { + "properties": { + "avg_size": { + "type": "float" + } + } + }, + "read": { + "properties": { + "await": { + "type": "float" + }, + "per_sec": { + "properties": { + "bytes": { + "type": "float" + } + } + }, + "request": { + "properties": { + "merges_per_sec": { + "type": "float" + }, + "per_sec": { + "type": "float" + } + } + } + } + }, + "request": { + "properties": { + "avg_size": { + "type": "float" + } + } + }, + "service_time": { + "type": "float" + }, + "write": { + "properties": { + "await": { + "type": "float" + }, + "per_sec": { + "properties": { + "bytes": { + "type": "float" + } + } + }, + "request": { + "properties": { + "merges_per_sec": { + "type": "float" + }, + "per_sec": { + "type": "float" + } + } + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "read": { + "properties": { + "bytes": { + "type": "long" + }, + "count": { + "type": "long" + }, + "time": { + "type": "long" + } + } + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "write": { + "properties": { + "bytes": { + "type": "long" + }, + "count": { + "type": "long" + }, + "time": { + "type": "long" + } + } + } + } + }, + "entropy": { + "properties": { + "available_bits": { + "type": "long" + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "filesystem": { + "properties": { + "available": { + "type": "long" + }, + "device_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "files": { + "type": "long" + }, + "free": { + "type": "long" + }, + "free_files": { + "type": "long" + }, + "mount_point": { + "ignore_above": 1024, + "type": "keyword" + }, + "total": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "used": { + "properties": { + "bytes": { + "type": "long" + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + }, + "fsstat": { + "properties": { + "count": { + "type": "long" + }, + "total_files": { + "type": "long" + }, + "total_size": { + "properties": { + "free": { + "type": "long" + }, + "total": { + "type": "long" + }, + "used": { + "type": "long" + } + } + } + } + }, + "load": { + "properties": { + "1": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "15": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "5": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "cores": { + "type": "long" + }, + "norm": { + "properties": { + "1": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "15": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "5": { + "scaling_factor": 100, + "type": "scaled_float" + } + } + } + } + }, + "memory": { + "properties": { + "actual": { + "properties": { + "free": { + "type": "long" + }, + "used": { + "properties": { + "bytes": { + "type": "long" + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + }, + "free": { + "type": "long" + }, + "hugepages": { + "properties": { + "default_size": { + "type": "long" + }, + "free": { + "type": "long" + }, + "reserved": { + "type": "long" + }, + "surplus": { + "type": "long" + }, + "total": { + "type": "long" + }, + "used": { + "properties": { + "bytes": { + "type": "long" + }, + "pct": { + "type": "long" + } + } + } + } + }, + "swap": { + "properties": { + "free": { + "type": "long" + }, + "total": { + "type": "long" + }, + "used": { + "properties": { + "bytes": { + "type": "long" + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + }, + "total": { + "type": "long" + }, + "used": { + "properties": { + "bytes": { + "type": "long" + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + } + } + }, + "network": { + "properties": { + "in": { + "properties": { + "bytes": { + "type": "long" + }, + "dropped": { + "type": "long" + }, + "errors": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "out": { + "properties": { + "bytes": { + "type": "long" + }, + "dropped": { + "type": "long" + }, + "errors": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + } + } + }, + "process": { + "properties": { + "cgroup": { + "properties": { + "blkio": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "total": { + "properties": { + "bytes": { + "type": "long" + }, + "ios": { + "type": "long" + } + } + } + } + }, + "cpu": { + "properties": { + "cfs": { + "properties": { + "period": { + "properties": { + "us": { + "type": "long" + } + } + }, + "quota": { + "properties": { + "us": { + "type": "long" + } + } + }, + "shares": { + "type": "long" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "rt": { + "properties": { + "period": { + "properties": { + "us": { + "type": "long" + } + } + }, + "runtime": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "stats": { + "properties": { + "periods": { + "type": "long" + }, + "throttled": { + "properties": { + "ns": { + "type": "long" + }, + "periods": { + "type": "long" + } + } + } + } + } + } + }, + "cpuacct": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "percpu": { + "properties": { + "1": { + "type": "long" + }, + "2": { + "type": "long" + }, + "3": { + "type": "long" + }, + "4": { + "type": "long" + } + } + }, + "stats": { + "properties": { + "system": { + "properties": { + "ns": { + "type": "long" + } + } + }, + "user": { + "properties": { + "ns": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "ns": { + "type": "long" + } + } + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "kmem": { + "properties": { + "failures": { + "type": "long" + }, + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "kmem_tcp": { + "properties": { + "failures": { + "type": "long" + }, + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "mem": { + "properties": { + "failures": { + "type": "long" + }, + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "memsw": { + "properties": { + "failures": { + "type": "long" + }, + "limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "usage": { + "properties": { + "bytes": { + "type": "long" + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "stats": { + "properties": { + "active_anon": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "active_file": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "cache": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "hierarchical_memory_limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "hierarchical_memsw_limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "inactive_anon": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "inactive_file": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "major_page_faults": { + "type": "long" + }, + "mapped_file": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "page_faults": { + "type": "long" + }, + "pages_in": { + "type": "long" + }, + "pages_out": { + "type": "long" + }, + "rss": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "rss_huge": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "swap": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "unevictable": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "cmdline": { + "ignore_above": 2048, + "type": "keyword" + }, + "cpu": { + "properties": { + "start_time": { + "type": "date" + }, + "system": { + "properties": { + "ticks": { + "type": "long" + } + } + }, + "total": { + "properties": { + "norm": { + "properties": { + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + }, + "ticks": { + "type": "long" + }, + "value": { + "type": "long" + } + } + }, + "user": { + "properties": { + "ticks": { + "type": "long" + } + } + } + } + }, + "env": { + "type": "object" + }, + "fd": { + "properties": { + "limit": { + "properties": { + "hard": { + "type": "long" + }, + "soft": { + "type": "long" + } + } + }, + "open": { + "type": "long" + } + } + }, + "memory": { + "properties": { + "rss": { + "properties": { + "bytes": { + "type": "long" + }, + "pct": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "share": { + "type": "long" + }, + "size": { + "type": "long" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "summary": { + "properties": { + "dead": { + "type": "long" + }, + "idle": { + "type": "long" + }, + "running": { + "type": "long" + }, + "sleeping": { + "type": "long" + }, + "stopped": { + "type": "long" + }, + "total": { + "type": "long" + }, + "unknown": { + "type": "long" + }, + "zombie": { + "type": "long" + } + } + } + } + }, + "raid": { + "properties": { + "blocks": { + "properties": { + "synced": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "disks": { + "properties": { + "active": { + "type": "long" + }, + "failed": { + "type": "long" + }, + "spare": { + "type": "long" + }, + "states": { + "properties": { + "*": { + "type": "object" + } + } + }, + "total": { + "type": "long" + } + } + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "sync_action": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "socket": { + "properties": { + "local": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "process": { + "properties": { + "cmdline": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "remote": { + "properties": { + "etld_plus_one": { + "ignore_above": 1024, + "type": "keyword" + }, + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "host_error": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "summary": { + "properties": { + "all": { + "properties": { + "count": { + "type": "long" + }, + "listening": { + "type": "long" + } + } + }, + "tcp": { + "properties": { + "all": { + "properties": { + "close_wait": { + "type": "long" + }, + "count": { + "type": "long" + }, + "established": { + "type": "long" + }, + "listening": { + "type": "long" + }, + "orphan": { + "type": "long" + }, + "time_wait": { + "type": "long" + } + } + }, + "memory": { + "type": "long" + } + } + }, + "udp": { + "properties": { + "all": { + "properties": { + "count": { + "type": "long" + } + } + }, + "memory": { + "type": "long" + } + } + } + } + } + } + }, + "uptime": { + "properties": { + "duration": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "timeseries": { + "properties": { + "instance": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "traefik": { + "properties": { + "health": { + "properties": { + "response": { + "properties": { + "avg_time": { + "properties": { + "us": { + "type": "long" + } + } + }, + "count": { + "type": "long" + }, + "status_codes": { + "properties": { + "*": { + "type": "object" + } + } + } + } + }, + "uptime": { + "properties": { + "sec": { + "type": "long" + } + } + } + } + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "uwsgi": { + "properties": { + "status": { + "properties": { + "core": { + "properties": { + "id": { + "type": "long" + }, + "read_errors": { + "type": "long" + }, + "requests": { + "properties": { + "offloaded": { + "type": "long" + }, + "routed": { + "type": "long" + }, + "static": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "worker_pid": { + "type": "long" + }, + "write_errors": { + "type": "long" + } + } + }, + "total": { + "properties": { + "exceptions": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "read_errors": { + "type": "long" + }, + "requests": { + "type": "long" + }, + "write_errors": { + "type": "long" + } + } + }, + "worker": { + "properties": { + "accepting": { + "type": "long" + }, + "avg_rt": { + "type": "long" + }, + "delta_requests": { + "type": "long" + }, + "exceptions": { + "type": "long" + }, + "harakiri_count": { + "type": "long" + }, + "id": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "requests": { + "type": "long" + }, + "respawn_count": { + "type": "long" + }, + "rss": { + "ignore_above": 1024, + "type": "keyword" + }, + "running_time": { + "type": "long" + }, + "signal_queue": { + "type": "long" + }, + "signals": { + "type": "long" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "tx": { + "type": "long" + }, + "vsz": { + "type": "long" + } + } + } + } + } + } + }, + "vsphere": { + "properties": { + "datastore": { + "properties": { + "capacity": { + "properties": { + "free": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + }, + "pct": { + "type": "long" + } + } + } + } + }, + "fstype": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "cpu": { + "properties": { + "free": { + "properties": { + "mhz": { + "type": "long" + } + } + }, + "total": { + "properties": { + "mhz": { + "type": "long" + } + } + }, + "used": { + "properties": { + "mhz": { + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "free": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "network_names": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "virtualmachine": { + "properties": { + "cpu": { + "properties": { + "used": { + "properties": { + "mhz": { + "type": "long" + } + } + } + } + }, + "custom_fields": { + "type": "object" + }, + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "memory": { + "properties": { + "free": { + "properties": { + "guest": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "guest": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "used": { + "properties": { + "guest": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "host": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "network_names": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "windows": { + "properties": { + "service": { + "properties": { + "display_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "start_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "zookeeper": { + "properties": { + "connection": { + "properties": { + "interest_ops": { + "type": "long" + }, + "queued": { + "type": "long" + }, + "received": { + "type": "long" + }, + "sent": { + "type": "long" + } + } + }, + "mntr": { + "properties": { + "approximate_data_size": { + "type": "long" + }, + "ephemerals_count": { + "type": "long" + }, + "followers": { + "type": "long" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "latency": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + }, + "max_file_descriptor_count": { + "type": "long" + }, + "num_alive_connections": { + "type": "long" + }, + "open_file_descriptor_count": { + "type": "long" + }, + "outstanding_requests": { + "type": "long" + }, + "packets": { + "properties": { + "received": { + "type": "long" + }, + "sent": { + "type": "long" + } + } + }, + "pending_syncs": { + "type": "long" + }, + "server_state": { + "ignore_above": 1024, + "type": "keyword" + }, + "synced_followers": { + "type": "long" + }, + "version": { + "path": "service.version", + "type": "alias" + }, + "watch_count": { + "type": "long" + }, + "znode_count": { + "type": "long" + } + } + }, + "server": { + "properties": { + "connections": { + "type": "long" + }, + "count": { + "type": "long" + }, + "epoch": { + "type": "long" + }, + "latency": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "node_count": { + "type": "long" + }, + "outstanding": { + "type": "long" + }, + "received": { + "type": "long" + }, + "sent": { + "type": "long" + }, + "version_date": { + "type": "date" + }, + "zxid": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "query": { + "default_field": [ + "beat.*", + "type", + "tags", + "meta.*", + "message" + ] + } + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/apm_api_integration/common/registry.ts b/x-pack/test/apm_api_integration/common/registry.ts index 7611d054852df..177600208b73d 100644 --- a/x-pack/test/apm_api_integration/common/registry.ts +++ b/x-pack/test/apm_api_integration/common/registry.ts @@ -17,6 +17,7 @@ type ArchiveName = | 'apm_8.0.0' | '8.0.0' | 'metrics_8.0.0' + | 'infra_metrics_and_apm' | 'ml_8.0.0' | 'observability_overview' | 'rum_8.0.0' diff --git a/x-pack/test/apm_api_integration/tests/services/service_details/service_details.spec.ts b/x-pack/test/apm_api_integration/tests/services/service_details/service_details.spec.ts index f54fbf26c7b99..1933d3552e611 100644 --- a/x-pack/test/apm_api_integration/tests/services/service_details/service_details.spec.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_details/service_details.spec.ts @@ -87,12 +87,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns correct container details', () => { - const { containerOs } = dataConfig; - - expect(body?.container?.isContainerized).to.be(true); - expect(body?.container?.os).to.be(containerOs); expect(body?.container?.totalNumberInstances).to.be(1); - expect(body?.container?.type).to.be('Kubernetes'); }); it('returns correct serverless details', () => { diff --git a/x-pack/test/apm_api_integration/tests/services/service_details/service_infra_metrics.spec.ts b/x-pack/test/apm_api_integration/tests/services/service_details/service_infra_metrics.spec.ts new file mode 100644 index 0000000000000..0a5d2f88453a6 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/service_details/service_infra_metrics.spec.ts @@ -0,0 +1,170 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; + +type ServiceOverviewInstanceDetails = + APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}'>; + +type ServiceDetails = APIReturnType<'GET /internal/apm/services/{serviceName}/metadata/details'>; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const archiveName = 'infra_metrics_and_apm'; + + const { start, end } = archives_metadata[archiveName]; + + registry.when( + 'When data is loaded', + { config: 'basic', archives: ['infra_metrics_and_apm'] }, + () => { + describe('fetch service instance', () => { + it('handles empty infra metrics data for a service node', async () => { + const response = await apmApiClient.readUser({ + endpoint: + 'GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}', + params: { + path: { + serviceName: 'opbeans-node', + serviceNodeName: '768120daead4526f5ba3ec583e0b081a19a525843aa5632a5e0b1de3a367f52d', + }, + query: { + start, + end, + }, + }, + }); + + const body: ServiceOverviewInstanceDetails = response.body; + const status: number = response.status; + + expect(status).to.be(200); + + expect(body.kubernetes?.pod).to.eql({}); + expect(body.kubernetes?.deployment).to.eql({}); + expect(body.kubernetes?.replicaset).to.eql({}); + expect(body.kubernetes?.container).to.eql({}); + }); + + it('handles kubernetes metadata for a service node', async () => { + const response = await apmApiClient.readUser({ + endpoint: + 'GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}', + params: { + path: { + serviceName: 'opbeans-java', + serviceNodeName: '31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad', + }, + query: { + start, + end, + }, + }, + }); + + const body: ServiceOverviewInstanceDetails = response.body; + const status: number = response.status; + + expect(status).to.be(200); + + expect(body.kubernetes?.deployment?.name).to.eql('opbeans-java'); + expect(body.kubernetes?.pod?.name).to.eql('opbeans-java-5b5f75d696-5brrb'); + expect(body.kubernetes?.pod?.uid).to.eql('798f59e9-b1b2-11e9-9a96-42010a84004d'); + expect(body.kubernetes?.namespace).to.eql('default'); + expect(body.kubernetes?.replicaset?.name).to.eql('opbeans-java-5b5f75d696'); + expect(body.kubernetes?.container?.name).to.eql('opbeans-java'); + }); + }); + + describe('fetch service overview metadata details', () => { + it('handles service overview metadata with multiple kubernetes instances', async () => { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/metadata/details', + params: { + path: { + serviceName: 'opbeans-java', + }, + query: { + start, + end, + }, + }, + }); + + const body: ServiceDetails = response.body; + const status: number = response.status; + + expect(status).to.be(200); + expect(body.kubernetes?.deployments).to.eql(['opbeans-java', 'opbeans-java-2']); + expect(body.kubernetes?.namespaces).to.eql(['default']); + expect(body.kubernetes?.containerImages).to.eql([ + 'docker.elastic.co/observability-ci/opbeans-java@sha256:dda30dbabe5c43b8bcd62b48a727f04e9d17147443ea3b3ac2edfc44cb0e69fe', + 'mysql@sha256:c8f03238ca1783d25af320877f063a36dbfce0daa56a7b4955e6c6e05ab5c70b', + ]); + expect(body.kubernetes?.replicasets).to.eql([ + 'opbeans-java-5b5f75d696', + 'opbeans-java-5b5f75d697', + ]); + }); + + it('handles partial infra metrics data', async () => { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/metadata/details', + params: { + path: { + serviceName: 'opbeans-node', + }, + query: { + start, + end, + }, + }, + }); + + const body: ServiceDetails = response.body; + const status: number = response.status; + + expect(status).to.be(200); + expect(body.kubernetes?.containerImages).to.eql([ + 'docker.elastic.co/observability-ci/opbeans-node@sha256:f72b0bfdd0ca24e4f9d10ee73cf713a591dbfa40f1fe9404b04e6f2f3e166949', + 'k8s.gcr.io/pause:3.1', + ]); + expect(body.kubernetes?.deployments).to.eql([]); + expect(body.kubernetes?.namespaces).to.eql([]); + expect(body.kubernetes?.replicasets).to.eql([]); + }); + + it('handles empty infra metrics data', async () => { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/metadata/details', + params: { + path: { + serviceName: 'opbeans-ruby', + }, + query: { + start, + end, + }, + }, + }); + + const body: ServiceDetails = response.body; + const status: number = response.status; + + expect(status).to.be(200); + expect(body.kubernetes?.containerImages).to.eql([]); + expect(body.kubernetes?.namespaces).to.eql([]); + expect(body.kubernetes?.namespaces).to.eql([]); + expect(body.kubernetes?.replicasets).to.eql([]); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/transactions/trace_samples.spec.ts b/x-pack/test/apm_api_integration/tests/transactions/trace_samples.spec.ts index 833562977f692..b42b6e0bfb757 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/trace_samples.spec.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/trace_samples.spec.ts @@ -6,7 +6,6 @@ */ import expect from '@kbn/expect'; -import { sortBy } from 'lodash'; import archives from '../../common/fixtures/es_archiver/archives_metadata'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -68,68 +67,98 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expectSnapshot(response.body.traceSamples.length).toMatchInline(`15`); - expectSnapshot(sortBy(traceSamples, (sample) => sample.traceId)).toMatchInline(` + expectSnapshot(traceSamples).toMatchInline(` Array [ Object { - "traceId": "0996b09e42ad4dbfaaa6a069326c6e66", - "transactionId": "5721364b179716d0", + "score": 0, + "timestamp": "2021-08-03T07:19:11.880Z", + "traceId": "6d85d8f1bc4bbbfdb19cdba59d2fc164", + "transactionId": "d0a16f0f52f25d6b", }, Object { + "score": 0, + "timestamp": "2021-08-03T07:19:10.914Z", "traceId": "10d882b7118870015815a27c37892375", "transactionId": "0cf9db0b1e321239", }, Object { - "traceId": "2ca82e99453c58584c4b8de9a8ba4ec3", - "transactionId": "8fa2ca73976ce1e7", - }, - Object { + "score": 0, + "timestamp": "2021-08-03T07:17:50.702Z", "traceId": "45b3d1a86003938687a55e49bf3610b8", "transactionId": "a707456bda99ee98", }, Object { - "traceId": "4943691f87b7eb97d442d1ef33ca65c7", - "transactionId": "f6f4677d731e57c5", + "score": 0, + "timestamp": "2021-08-03T07:17:47.588Z", + "traceId": "2ca82e99453c58584c4b8de9a8ba4ec3", + "transactionId": "8fa2ca73976ce1e7", }, Object { - "traceId": "5267685738bf75b68b16bf3426ba858c", - "transactionId": "5223f43bc3154c5a", + "score": 0, + "timestamp": "2021-08-03T07:17:09.819Z", + "traceId": "a21ea39b41349a4614a86321d965c957", + "transactionId": "338bd7908cbf7f2d", }, Object { - "traceId": "66bd97c457f5675665397ac9201cc050", - "transactionId": "592b60cc9ddabb15", + "score": 0, + "timestamp": "2021-08-03T07:15:15.804Z", + "traceId": "ca7a2072e7974ae84b5096706c6b6255", + "transactionId": "92ab7f2ef11685dd", }, Object { - "traceId": "6d85d8f1bc4bbbfdb19cdba59d2fc164", - "transactionId": "d0a16f0f52f25d6b", + "score": 0, + "timestamp": "2021-08-03T07:15:00.171Z", + "traceId": "d250e2a1bad40f78653d8858db65326b", + "transactionId": "6fcd12599c1b57fa", }, Object { - "traceId": "7483bd52150d1c93a858c60bfdd0c138", - "transactionId": "e20e701ff93bdb55", + "score": 0, + "timestamp": "2021-08-03T07:14:34.640Z", + "traceId": "66bd97c457f5675665397ac9201cc050", + "transactionId": "592b60cc9ddabb15", }, Object { - "traceId": "9a84d15e5a0e32098d569948474e8e2f", - "transactionId": "b85db78a9824107b", + "score": 0, + "timestamp": "2021-08-03T07:11:55.249Z", + "traceId": "d9415d102c0634e1e8fa53ceef07be70", + "transactionId": "fab91c68c9b1c42b", }, Object { - "traceId": "a21ea39b41349a4614a86321d965c957", - "transactionId": "338bd7908cbf7f2d", + "score": 0, + "timestamp": "2021-08-03T07:03:29.734Z", + "traceId": "0996b09e42ad4dbfaaa6a069326c6e66", + "transactionId": "5721364b179716d0", }, Object { - "traceId": "ca7a2072e7974ae84b5096706c6b6255", - "transactionId": "92ab7f2ef11685dd", + "score": 0, + "timestamp": "2021-08-03T07:03:05.825Z", + "traceId": "7483bd52150d1c93a858c60bfdd0c138", + "transactionId": "e20e701ff93bdb55", }, Object { - "traceId": "d250e2a1bad40f78653d8858db65326b", - "transactionId": "6fcd12599c1b57fa", + "score": 0, + "timestamp": "2021-08-03T06:58:34.565Z", + "traceId": "4943691f87b7eb97d442d1ef33ca65c7", + "transactionId": "f6f4677d731e57c5", }, Object { - "traceId": "d9415d102c0634e1e8fa53ceef07be70", - "transactionId": "fab91c68c9b1c42b", + "score": 0, + "timestamp": "2021-08-03T06:55:02.016Z", + "traceId": "9a84d15e5a0e32098d569948474e8e2f", + "transactionId": "b85db78a9824107b", }, Object { + "score": 0, + "timestamp": "2021-08-03T06:54:37.915Z", "traceId": "e123f0466fa092f345d047399db65aa2", "transactionId": "c0af16286229d811", }, + Object { + "score": 0, + "timestamp": "2021-08-03T06:53:18.507Z", + "traceId": "5267685738bf75b68b16bf3426ba858c", + "transactionId": "5223f43bc3154c5a", + }, ] `); }); diff --git a/x-pack/test/cases_api_integration/common/lib/user_profiles.ts b/x-pack/test/cases_api_integration/common/lib/user_profiles.ts index 1ab565859f9e8..0c0a717809d30 100644 --- a/x-pack/test/cases_api_integration/common/lib/user_profiles.ts +++ b/x-pack/test/cases_api_integration/common/lib/user_profiles.ts @@ -20,6 +20,11 @@ import { getUserInfo } from './authentication'; type BulkGetUserProfilesParams = Omit & { uids: string[] }; +export const generateFakeAssignees = (num: number) => + Array.from(Array(num).keys()).map((uid) => { + return { uid: `${uid}` }; + }); + export const bulkGetUserProfiles = async ({ supertest, req, 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 5bafda42115c8..4b709998f1a2e 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 @@ -38,7 +38,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./metrics/get_case_metrics_actions')); loadTestFile(require.resolve('./metrics/get_case_metrics_connectors')); loadTestFile(require.resolve('./metrics/get_cases_metrics')); - loadTestFile(require.resolve('./user_profiles/get_current')); /** * Internal routes diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/assignees.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/assignees.ts index 53478adece67f..1835c88c4dafb 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/assignees.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/assignees.ts @@ -16,7 +16,7 @@ import { deleteAllCaseItems, } from '../../../../common/lib/utils'; -import { suggestUserProfiles } from '../../../../common/lib/user_profiles'; +import { generateFakeAssignees, suggestUserProfiles } from '../../../../common/lib/user_profiles'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { bulkGetUserProfiles } from '../../../../common/lib/user_profiles'; @@ -304,5 +304,35 @@ export default ({ getService }: FtrProviderContext): void => { const updatedCase = await getCase({ supertest, caseId: postedCase.id }); expect(updatedCase.assignees).to.eql([{ uid: '123' }]); }); + + describe('validation', () => { + it('validates correctly the number of assignees when creating a case', async () => { + await createCase( + supertest, + getPostCaseRequest({ + assignees: generateFakeAssignees(11), + }), + 400 + ); + }); + + it('validates correctly the number of assignees when updating a case', async () => { + const postedCase = await createCase(supertest, getPostCaseRequest()); + + await updateCase({ + supertest, + params: { + cases: [ + { + id: postedCase.id, + version: postedCase.version, + assignees: generateFakeAssignees(11), + }, + ], + }, + expectedHttpCode: 400, + }); + }); + }); }); }; 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 1d6e0c373c243..b9ee78736e3f3 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 @@ -33,6 +33,7 @@ export default ({ loadTestFile, getService }: FtrProviderContext): void => { loadTestFile(require.resolve('./configure')); // sub privileges are only available with a license above basic loadTestFile(require.resolve('./delete_sub_privilege')); + loadTestFile(require.resolve('./user_profiles/get_current')); // Internal routes loadTestFile(require.resolve('./internal/suggest_user_profiles')); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_profiles/get_current.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/user_profiles/get_current.ts similarity index 99% rename from x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_profiles/get_current.ts rename to x-pack/test/cases_api_integration/security_and_spaces/tests/trial/user_profiles/get_current.ts index 5478eab0bdbd6..9e070c3f4ffc9 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_profiles/get_current.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/user_profiles/get_current.ts @@ -30,8 +30,7 @@ export default function ({ getService }: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); const es = getService('es'); - // Failing: See https://github.com/elastic/kibana/issues/140812 - describe.skip('user_profiles', () => { + describe('user_profiles', () => { describe('get_current', () => { let headers: Record; let superUserWithProfile: User; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts index 1b9dcb5da28fd..a8dc735376f69 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts @@ -22,6 +22,7 @@ import { getSimpleMlRuleOutput, getSimpleRuleUpdate, getSimpleMlRuleUpdate, + getSimpleSavedQueryRule, createRule, getSimpleRule, createLegacyRuleAction, @@ -521,6 +522,53 @@ export default ({ getService }: FtrProviderContext) => { }); }); }); + + describe('saved_query and query rule type', () => { + it('should allow to save a query rule type as a saved_query rule type', async () => { + const ruleId = 'rule-1'; + const savedQueryRule = getSimpleSavedQueryRule(ruleId); + await createRule(supertest, log, getSimpleRule(ruleId)); + + const { body: outputRule } = await supertest + .put(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(savedQueryRule) + .expect(200); + + expect(outputRule.type).to.be('saved_query'); + expect(outputRule.saved_id).to.be(savedQueryRule.saved_id); + }); + + it('should allow to save a query rule type as a saved_query rule type with undefined query', async () => { + const ruleId = 'rule-1'; + const savedQueryRule = { ...getSimpleSavedQueryRule(ruleId), query: undefined }; + await createRule(supertest, log, getSimpleRule(ruleId)); + + const { body: outputRule } = await supertest + .put(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(savedQueryRule) + .expect(200); + + expect(outputRule.type).to.be('saved_query'); + expect(outputRule.saved_id).to.be(savedQueryRule.saved_id); + }); + + it('should allow to save a saved_query rule type as a query rule type', async () => { + const ruleId = 'rule-1'; + const queryRule = getSimpleRule(ruleId); + await createRule(supertest, log, getSimpleSavedQueryRule(ruleId)); + + const { body: outputRule } = await supertest + .put(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(queryRule) + .expect(200); + + expect(outputRule.type).to.be('query'); + expect(outputRule.saved_id).to.be(undefined); + }); + }); }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_saved_query_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_saved_query_rule.ts new file mode 100644 index 0000000000000..2fdd157c9c0d4 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_saved_query_rule.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { SavedQueryCreateSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + +/** + * This is a typical simple saved_query rule for e2e testing + * @param ruleId + * @param enabled Enables the rule on creation or not. Defaulted to true. + */ +export const getSimpleSavedQueryRule = ( + ruleId = 'rule-1', + enabled = false +): SavedQueryCreateSchema => ({ + name: 'Simple Saved Query Rule', + description: 'Simple Saved Query Rule', + enabled, + risk_score: 1, + rule_id: ruleId, + severity: 'high', + index: ['auditbeat-*'], + type: 'saved_query', + saved_id: 'mock_id', + query: 'user.name: root or user.name: admin', +}); diff --git a/x-pack/test/detection_engine_api_integration/utils/index.ts b/x-pack/test/detection_engine_api_integration/utils/index.ts index 81bad1b1b583b..31d13a52f72da 100644 --- a/x-pack/test/detection_engine_api_integration/utils/index.ts +++ b/x-pack/test/detection_engine_api_integration/utils/index.ts @@ -69,6 +69,7 @@ export * from './get_simple_rule_output_without_rule_id'; export * from './get_simple_rule_preview_output'; export * from './get_simple_rule_update'; export * from './get_simple_rule_without_rule_id'; +export * from './get_simple_saved_query_rule'; export * from './get_simple_threat_match'; export * from './get_stats'; export * from './get_stats_url'; diff --git a/x-pack/test/fleet_api_integration/apis/agents/action_status.ts b/x-pack/test/fleet_api_integration/apis/agents/action_status.ts new file mode 100644 index 0000000000000..f17d7ac625529 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/agents/action_status.ts @@ -0,0 +1,255 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '@kbn/fleet-plugin/common'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { setupFleetAndAgents } from './services'; +import { skipIfNoDockerRegistry } from '../../helpers'; + +const ES_INDEX_OPTIONS = { headers: { 'X-elastic-product-origin': 'fleet' } }; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const es = getService('es'); + const esArchiver = getService('esArchiver'); + + describe('action_status_api', () => { + skipIfNoDockerRegistry(providerContext); + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/fleet/agents'); + }); + setupFleetAndAgents(providerContext); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/fleet/agents'); + }); + + describe('GET /api/fleet/agents/action_status', () => { + before(async () => { + await es.deleteByQuery({ + index: AGENT_ACTIONS_INDEX, + q: '*', + }); + try { + await es.deleteByQuery( + { + index: AGENT_ACTIONS_RESULTS_INDEX, + q: '*', + }, + ES_INDEX_OPTIONS + ); + } catch (error) { + // swallowing error if does not exist + } + + // action 2 non expired and non complete + await es.index({ + refresh: 'wait_for', + index: AGENT_ACTIONS_INDEX, + document: { + type: 'UPGRADE', + action_id: 'action2', + agents: ['agent1', 'agent2', 'agent3'], + '@timestamp': '2022-09-15T10:00:00.000Z', + start_time: '2022-09-15T10:00:00.000Z', + expiration: '2099-09-16T10:00:00.000Z', + data: { + version: '8.5.0', + }, + }, + }); + + await es.index({ + refresh: 'wait_for', + index: AGENT_ACTIONS_INDEX, + document: { + type: 'UPGRADE', + action_id: 'action2', + agents: ['agent4', 'agent5'], + '@timestamp': '2022-09-15T10:00:00.000Z', + start_time: '2022-09-15T10:00:00.000Z', + expiration: '2099-09-16T10:00:00.000Z', + }, + }); + // Action 3 complete + await es.index({ + refresh: 'wait_for', + index: AGENT_ACTIONS_INDEX, + document: { + type: 'UPGRADE', + action_id: 'action3', + agents: ['agent1', 'agent2'], + '@timestamp': '2022-09-15T10:00:00.000Z', + expiration: '2099-09-16T10:00:00.000Z', + }, + }); + await es.index( + { + refresh: 'wait_for', + index: AGENT_ACTIONS_RESULTS_INDEX, + document: { + action_id: 'action3', + agent_id: 'agent1', + '@timestamp': '2022-09-15T11:00:00.000Z', + }, + }, + ES_INDEX_OPTIONS + ); + await es.index( + { + refresh: 'wait_for', + index: AGENT_ACTIONS_RESULTS_INDEX, + document: { + action_id: 'action3', + agent_id: 'agent2', + '@timestamp': '2022-09-15T12:00:00.000Z', + }, + }, + ES_INDEX_OPTIONS + ); + + // Action 4 expired + await es.index({ + refresh: 'wait_for', + index: AGENT_ACTIONS_INDEX, + document: { + type: 'UNENROLL', + action_id: 'action4', + agents: ['agent1', 'agent2', 'agent3'], + '@timestamp': '2022-09-15T10:00:00.000Z', + expiration: '2022-09-14T10:00:00.000Z', + }, + }); + + // Action 5 cancelled + await es.index({ + refresh: 'wait_for', + index: AGENT_ACTIONS_INDEX, + document: { + type: 'UPGRADE', + action_id: 'action5', + agents: ['agent1', 'agent2', 'agent3'], + '@timestamp': '2022-09-15T10:00:00.000Z', + start_time: '2022-09-15T10:00:00.000Z', + expiration: '2099-09-16T10:00:00.000Z', + }, + }); + await es.index({ + refresh: 'wait_for', + index: AGENT_ACTIONS_INDEX, + document: { + '@timestamp': '2022-09-15T11:00:00.000Z', + type: 'CANCEL', + action_id: 'cancelaction1', + agents: ['agent1', 'agent2', 'agent3'], + expiration: '2099-09-16T10:00:00.000Z', + data: { + target_id: 'action5', + }, + }, + }); + + // Action 7 failed + await es.index({ + refresh: 'wait_for', + index: AGENT_ACTIONS_INDEX, + document: { + type: 'POLICY_REASSIGN', + action_id: 'action7', + agents: ['agent1'], + '@timestamp': '2022-09-15T10:00:00.000Z', + expiration: '2099-09-16T10:00:00.000Z', + data: { + policy_id: 'policy1', + }, + }, + }); + await es.index( + { + refresh: 'wait_for', + index: AGENT_ACTIONS_RESULTS_INDEX, + document: { + action_id: 'action7', + agent_id: 'agent1', + '@timestamp': '2022-09-15T11:00:00.000Z', + error: 'agent already assigned', + }, + }, + ES_INDEX_OPTIONS + ); + }); + it('should respond 200 and the action statuses', async () => { + const res = await supertest.get(`/api/fleet/agents/action_status`).expect(200); + expect(res.body.items).to.eql([ + { + actionId: 'action2', + nbAgentsActionCreated: 5, + nbAgentsAck: 0, + version: '8.5.0', + startTime: '2022-09-15T10:00:00.000Z', + type: 'UPGRADE', + nbAgentsActioned: 5, + status: 'IN_PROGRESS', + expiration: '2099-09-16T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + }, + { + actionId: 'action3', + nbAgentsActionCreated: 2, + nbAgentsAck: 2, + type: 'UPGRADE', + nbAgentsActioned: 2, + status: 'COMPLETE', + expiration: '2099-09-16T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + completionTime: '2022-09-15T12:00:00.000Z', + }, + { + actionId: 'action4', + nbAgentsActionCreated: 3, + nbAgentsAck: 0, + type: 'UNENROLL', + nbAgentsActioned: 3, + status: 'EXPIRED', + expiration: '2022-09-14T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + }, + { + actionId: 'action5', + nbAgentsActionCreated: 3, + nbAgentsAck: 0, + startTime: '2022-09-15T10:00:00.000Z', + type: 'UPGRADE', + nbAgentsActioned: 3, + status: 'CANCELLED', + expiration: '2099-09-16T10:00:00.000Z', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 0, + cancellationTime: '2022-09-15T11:00:00.000Z', + }, + { + actionId: 'action7', + nbAgentsActionCreated: 1, + nbAgentsAck: 0, + type: 'POLICY_REASSIGN', + nbAgentsActioned: 1, + status: 'FAILED', + expiration: '2099-09-16T10:00:00.000Z', + newPolicyId: 'policy1', + creationTime: '2022-09-15T10:00:00.000Z', + nbAgentsFailed: 1, + completionTime: '2022-09-15T11:00:00.000Z', + }, + ]); + }); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/agents/current_upgrades.ts b/x-pack/test/fleet_api_integration/apis/agents/current_upgrades.ts deleted file mode 100644 index 9d7e7e655c7a1..0000000000000 --- a/x-pack/test/fleet_api_integration/apis/agents/current_upgrades.ts +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import moment from 'moment'; -import { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '@kbn/fleet-plugin/common'; -import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; -import { setupFleetAndAgents } from './services'; -import { skipIfNoDockerRegistry } from '../../helpers'; - -const ES_INDEX_OPTIONS = { headers: { 'X-elastic-product-origin': 'fleet' } }; - -export default function (providerContext: FtrProviderContext) { - const { getService } = providerContext; - const supertest = getService('supertest'); - const es = getService('es'); - const esArchiver = getService('esArchiver'); - - describe('Agent current upgrades API', () => { - skipIfNoDockerRegistry(providerContext); - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/fleet/agents'); - }); - setupFleetAndAgents(providerContext); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/fleet/agents'); - }); - - describe('GET /api/fleet/agents/current_upgrades', () => { - before(async () => { - await es.deleteByQuery({ - index: AGENT_ACTIONS_INDEX, - q: '*', - }); - // Action 1 non expired and non complete - await es.index({ - refresh: 'wait_for', - index: AGENT_ACTIONS_INDEX, - document: { - type: 'UPGRADE', - action_id: 'action1', - agents: ['agent1', 'agent2', 'agent3'], - start_time: moment().toISOString(), - expiration: moment().add(1, 'day').toISOString(), - }, - }); - - // action 2 non expired and non complete - await es.index({ - refresh: 'wait_for', - index: AGENT_ACTIONS_INDEX, - document: { - type: 'UPGRADE', - action_id: 'action2', - agents: ['agent1', 'agent2', 'agent3'], - start_time: moment().toISOString(), - expiration: moment().add(1, 'day').toISOString(), - }, - }); - - await es.index({ - refresh: 'wait_for', - index: AGENT_ACTIONS_INDEX, - document: { - type: 'UPGRADE', - action_id: 'action2', - agents: ['agent4', 'agent5'], - start_time: moment().toISOString(), - expiration: moment().add(1, 'day').toISOString(), - }, - }); - // Action 3 complete - await es.index({ - refresh: 'wait_for', - index: AGENT_ACTIONS_INDEX, - document: { - type: 'UPGRADE', - action_id: 'action3', - agents: ['agent1', 'agent2'], - start_time: moment().toISOString(), - expiration: moment().add(1, 'day').toISOString(), - }, - }); - await es.index( - { - refresh: 'wait_for', - index: AGENT_ACTIONS_RESULTS_INDEX, - document: { - action_id: 'action3', - '@timestamp': new Date().toISOString(), - started_at: new Date().toISOString(), - completed_at: new Date().toISOString(), - }, - }, - ES_INDEX_OPTIONS - ); - await es.index( - { - refresh: 'wait_for', - index: AGENT_ACTIONS_RESULTS_INDEX, - document: { - action_id: 'action3', - '@timestamp': new Date().toISOString(), - started_at: new Date().toISOString(), - completed_at: new Date().toISOString(), - }, - }, - ES_INDEX_OPTIONS - ); - - // Action 4 expired - await es.index({ - refresh: 'wait_for', - index: AGENT_ACTIONS_INDEX, - document: { - type: 'UPGRADE', - action_id: 'action4', - agents: ['agent1', 'agent2', 'agent3'], - expiration: moment().subtract(1, 'day').toISOString(), - }, - }); - - // Action 5 cancelled - await es.index({ - refresh: 'wait_for', - index: AGENT_ACTIONS_INDEX, - document: { - type: 'UPGRADE', - action_id: 'action5', - agents: ['agent1', 'agent2', 'agent3'], - start_time: moment().toISOString(), - expiration: moment().add(1, 'day').toISOString(), - }, - }); - await es.index({ - refresh: 'wait_for', - index: AGENT_ACTIONS_INDEX, - document: { - type: 'CANCEL', - action_id: 'cancelaction1', - agents: ['agent1', 'agent2', 'agent3'], - expiration: moment().add(1, 'day').toISOString(), - data: { - target_id: 'action5', - }, - }, - }); - - // Action 6 1 agent with not start time - await es.index({ - refresh: 'wait_for', - index: AGENT_ACTIONS_INDEX, - document: { - type: 'UPGRADE', - action_id: 'action6', - agents: ['agent1'], - expiration: moment().add(1, 'day').toISOString(), - }, - }); - }); - it('should respond 200 and the current upgrades', async () => { - const res = await supertest.get(`/api/fleet/agents/current_upgrades`).expect(200); - const actionIds = res.body.items.map((item: any) => item.actionId); - expect(actionIds).length(3); - expect(actionIds).contain('action1'); - expect(actionIds).contain('action2'); - expect(actionIds).contain('action6'); - }); - }); - }); -} diff --git a/x-pack/test/fleet_api_integration/apis/agents/index.js b/x-pack/test/fleet_api_integration/apis/agents/index.js index a1904c2dd317a..0ce4413f7c2cf 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/index.js +++ b/x-pack/test/fleet_api_integration/apis/agents/index.js @@ -12,7 +12,7 @@ export default function loadTests({ loadTestFile }) { loadTestFile(require.resolve('./unenroll')); loadTestFile(require.resolve('./actions')); loadTestFile(require.resolve('./upgrade')); - loadTestFile(require.resolve('./current_upgrades')); + loadTestFile(require.resolve('./action_status')); loadTestFile(require.resolve('./reassign')); loadTestFile(require.resolve('./status')); loadTestFile(require.resolve('./update')); diff --git a/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts b/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts index 865b095da534e..f459ca8123e42 100644 --- a/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts +++ b/x-pack/test/functional/apps/aiops/explain_log_rate_spikes.ts @@ -130,7 +130,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { }); } - describe('explain log rate spikes', function () { + // Failing: See https://github.com/elastic/kibana/issues/140848 + describe.skip('explain log rate spikes', function () { this.tags(['aiops']); before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); diff --git a/x-pack/test/functional/apps/dashboard/group2/dashboard_tagging.ts b/x-pack/test/functional/apps/dashboard/group2/dashboard_tagging.ts index ca0093135b7da..535e287cfc5eb 100644 --- a/x-pack/test/functional/apps/dashboard/group2/dashboard_tagging.ts +++ b/x-pack/test/functional/apps/dashboard/group2/dashboard_tagging.ts @@ -38,7 +38,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { `tag-searchbar-option-${PageObjects.tagManagement.testSubjFriendly(dashboardTag)}` ); // click elsewhere to close the filter dropdown - const searchFilter = await find.byCssSelector('.euiPageBody .euiFieldSearch'); + const searchFilter = await find.byCssSelector('.euiPageTemplate .euiFieldSearch'); await searchFilter.click(); // wait until the table refreshes await listingTable.waitUntilTableIsLoaded(); diff --git a/x-pack/test/functional/apps/lens/group3/annotations.ts b/x-pack/test/functional/apps/lens/group3/annotations.ts index e139677737b42..e090385dfc91a 100644 --- a/x-pack/test/functional/apps/lens/group3/annotations.ts +++ b/x-pack/test/functional/apps/lens/group3/annotations.ts @@ -78,5 +78,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.lens.closeDimensionEditor(); await testSubjects.existOrFail('xyVisGroupedAnnotationIcon'); }); + + it('should add query annotation layer and allow edition', async () => { + await PageObjects.lens.removeLayer(1); + await PageObjects.lens.createLayer('annotations'); + + expect((await find.allByCssSelector(`[data-test-subj^="lns-layerPanel-"]`)).length).to.eql(2); + expect( + await ( + await testSubjects.find('lnsXY_xAnnotationsPanel > lns-dimensionTrigger') + ).getVisibleText() + ).to.eql('Event'); + await testSubjects.click('lnsXY_xAnnotationsPanel > lns-dimensionTrigger'); + await testSubjects.click('lnsXY_annotation_query'); + await PageObjects.lens.configureQueryAnnotation({ + queryString: '*', + timeField: 'utc_time', + textDecoration: { type: 'name' }, + extraFields: ['clientip'], + }); + await PageObjects.lens.closeDimensionEditor(); + + await testSubjects.existOrFail('xyVisGroupedAnnotationIcon'); + }); }); } diff --git a/x-pack/test/functional/apps/lens/group3/error_handling.ts b/x-pack/test/functional/apps/lens/group3/error_handling.ts index 794547fb96f05..4d63e6d12306c 100644 --- a/x-pack/test/functional/apps/lens/group3/error_handling.ts +++ b/x-pack/test/functional/apps/lens/group3/error_handling.ts @@ -64,6 +64,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.lens.waitForMissingDataViewWarningDisappear(); await PageObjects.lens.waitForEmptyWorkspace(); }); + + it('works fine when the dataViews is missing for referenceLines and annotations', async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName( + 'lnsXYWithReferenceLinesAndAnnotationsWithNonExistingDataView' + ); + await PageObjects.lens.clickVisualizeListItemTitle( + 'lnsXYWithReferenceLinesAndAnnotationsWithNonExistingDataView' + ); + await PageObjects.lens.waitForMissingDataViewWarning(); + }); }); }); } diff --git a/x-pack/test/functional/apps/lens/group3/lens_tagging.ts b/x-pack/test/functional/apps/lens/group3/lens_tagging.ts index d69b49403fc31..d4d9f084a64f7 100644 --- a/x-pack/test/functional/apps/lens/group3/lens_tagging.ts +++ b/x-pack/test/functional/apps/lens/group3/lens_tagging.ts @@ -112,7 +112,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { `tag-searchbar-option-${PageObjects.tagManagement.testSubjFriendly(lensTag)}` ); // click elsewhere to close the filter dropdown - const searchFilter = await find.byCssSelector('.euiPageBody .euiFieldSearch'); + const searchFilter = await find.byCssSelector('.euiPageTemplate .euiFieldSearch'); await searchFilter.click(); // wait until the table refreshes await listingTable.waitUntilTableIsLoaded(); diff --git a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts index b7e292e945a4f..1745694e30831 100644 --- a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts @@ -50,6 +50,9 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep('should display the enabled "Overview" tab'); await ml.navigation.assertOverviewTabEnabled(true); + await ml.testExecution.logTestStep('should display the enabled "Notifications" tab'); + await ml.navigation.assertNotificationsTabEnabled(true); + await ml.testExecution.logTestStep( 'should display the enabled "Anomaly Detection" section correctly' ); diff --git a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts index bc4c81ccf7f2d..10477c5a4797a 100644 --- a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts @@ -50,6 +50,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await ml.testExecution.logTestStep('should display the enabled "Overview" tab'); await ml.navigation.assertOverviewTabEnabled(true); + await ml.testExecution.logTestStep('should display the enabled "Notifications" tab'); + await ml.navigation.assertNotificationsTabEnabled(true); + await ml.testExecution.logTestStep( 'should display the enabled "Anomaly Detection" section correctly' ); diff --git a/x-pack/test/functional/apps/ml/short_tests/pages.ts b/x-pack/test/functional/apps/ml/short_tests/pages.ts index d81b5933d77df..e17454d0fa222 100644 --- a/x-pack/test/functional/apps/ml/short_tests/pages.ts +++ b/x-pack/test/functional/apps/ml/short_tests/pages.ts @@ -24,6 +24,9 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep('loads the overview page'); await ml.navigation.navigateToOverview(); + await ml.testExecution.logTestStep('loads the notifications page'); + await ml.navigation.navigateToNotifications(); + await ml.testExecution.logTestStep('loads the anomaly detection area'); await ml.navigation.navigateToAnomalyDetection(); diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts index 4dee8e3af8262..f23ebdde93d9b 100644 --- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts @@ -21,7 +21,8 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const transform = getService('transform'); - describe('creation_index_pattern', function () { + // Failing: See https://github.com/elastic/kibana/issues/139781 + describe.skip('creation_index_pattern', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/ecommerce'); await transform.testResources.createIndexPatternIfNeeded('ft_ecommerce', 'order_date'); diff --git a/x-pack/test/functional/fixtures/kbn_archiver/lens/errors.json b/x-pack/test/functional/fixtures/kbn_archiver/lens/errors.json index 9ecc14164d863..49cfc4715ad4c 100644 --- a/x-pack/test/functional/fixtures/kbn_archiver/lens/errors.json +++ b/x-pack/test/functional/fixtures/kbn_archiver/lens/errors.json @@ -75,4 +75,199 @@ "type": "lens", "updated_at": "2021-10-19T13:41:04.038Z", "version": "WzU2NjEsMl0=" +} + +{ + "attributes": { + "description": "", + "state": { + "datasourceStates": { + "indexpattern": { + "layers": { + "3c85b7f0-3227-43e7-88ac-9416c6311ebc": { + "columns": { + "951ad12c-8fae-4e81-964d-84827e387515": { + "label": "order_date", + "dataType": "date", + "operationType": "date_histogram", + "sourceField": "order_date", + "isBucketed": true, + "scale": "interval", + "params": { + "interval": "auto", + "includeEmptyRows": true, + "dropPartials": false + } + }, + "e311d921-2525-4fb1-9716-94fe787ad623": { + "label": "Count of records", + "dataType": "number", + "operationType": "count", + "isBucketed": false, + "scale": "ratio", + "sourceField": "___records___", + "params": { + "emptyAsNull": true + } + } + }, + "columnOrder": [ + "951ad12c-8fae-4e81-964d-84827e387515", + "e311d921-2525-4fb1-9716-94fe787ad623" + ], + "incompleteColumns": {} + }, + "5321ae4b-8f8a-4300-a9bc-ec7245e2cb0f": { + "columns": { + "735cacfd-52af-4ff9-afa5-0e14c1b7c7fd": { + "label": "Static value: 127.5", + "dataType": "number", + "operationType": "static_value", + "isStaticValue": true, + "isBucketed": false, + "scale": "ratio", + "params": { + "value": "127.5" + }, + "references": [] + } + }, + "columnOrder": [ + "735cacfd-52af-4ff9-afa5-0e14c1b7c7fd" + ], + "incompleteColumns": {} + } + } + } + + }, + "filters": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "legend": { + "isVisible": true, + "position": "right" + }, + "valueLabels": "hide", + "fittingFunction": "None", + "axisTitlesVisibilitySettings": { + "x": true, + "yLeft": true, + "yRight": true + }, + "tickLabelsVisibilitySettings": { + "x": true, + "yLeft": true, + "yRight": true + }, + "labelsOrientation": { + "x": 0, + "yLeft": 0, + "yRight": 0 + }, + "gridlinesVisibilitySettings": { + "x": true, + "yLeft": true, + "yRight": true + }, + "preferredSeriesType": "bar_stacked", + "layers": [ + { + "layerId": "3c85b7f0-3227-43e7-88ac-9416c6311ebc", + "accessors": [ + "e311d921-2525-4fb1-9716-94fe787ad623" + ], + "position": "top", + "seriesType": "bar_stacked", + "showGridlines": false, + "layerType": "data", + "xAccessor": "951ad12c-8fae-4e81-964d-84827e387515" + }, + { + "layerId": "5321ae4b-8f8a-4300-a9bc-ec7245e2cb0f", + "layerType": "referenceLine", + "accessors": [ + "735cacfd-52af-4ff9-afa5-0e14c1b7c7fd" + ], + "yConfig": [ + { + "forAccessor": "735cacfd-52af-4ff9-afa5-0e14c1b7c7fd", + "axisMode": "left" + } + ] + }, + { + "layerId": "396c620c-1b6b-4754-a8fa-7f0e830a825c", + "layerType": "annotations", + "annotations": [ + { + "label": "Event", + "type": "manual", + "key": { + "type": "point_in_time", + "timestamp": "2022-07-25T22:00:00.000Z" + }, + "icon": "triangle", + "id": "13354257-3cd4-46b5-9462-d3fbbab6a433" + }, + { + "type": "query", + "id": "06539b10-c487-4aba-bf21-761c014c8d60", + "label": "Event", + "key": { + "type": "point_in_time" + }, + "icon": "triangle", + "textVisibility": true, + "textField": "customer_gender", + "filter": { + "type": "kibana_query", + "query": "*", + "language": "kuery" + }, + "extraFields": [ + "category.keyword" + ] + } + ] + } + ] + } + }, + "title": "lnsXYWithReferenceLinesAndAnnotationsWithNonExistingDataView", + "visualizationType": "lnsXY" + }, + "coreMigrationVersion": "8.0.0", + "id": "3454af30-30e2-11ec-8dbc-f13e30d4f8ac1", + "migrationVersion": { + "lens": "8.0.0" + }, + "references": [ + { + "id": "nonExistingDataView", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern" + }, + { + "id": "nonExistingDataView", + "name": "indexpattern-datasource-layer-3c85b7f0-3227-43e7-88ac-9416c6311ebc", + "type": "index-pattern" + }, + { + "id": "nonExistingDataView", + "name": "indexpattern-datasource-layer-5321ae4b-8f8a-4300-a9bc-ec7245e2cb0f", + "type": "index-pattern" + }, + { + "id": "nonExistingDataView", + "name": "xy-visualization-layer-396c620c-1b6b-4754-a8fa-7f0e830a825c", + "type": "index-pattern" + } + ], + "type": "lens", + "updated_at": "2021-10-19T13:41:04.038Z", + "version": "WzU2NjEsMl0=" } \ No newline at end of file diff --git a/x-pack/test/functional/page_objects/infra_home_page.ts b/x-pack/test/functional/page_objects/infra_home_page.ts index f9b57c636f341..71be94e4ed338 100644 --- a/x-pack/test/functional/page_objects/infra_home_page.ts +++ b/x-pack/test/functional/page_objects/infra_home_page.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import testSubjSelector from '@kbn/test-subj-selector'; +import { subj as testSubjSelector } from '@kbn/test-subj-selector'; import { FtrProviderContext } from '../ftr_provider_context'; diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 20412284aa92f..abcf958e87128 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -104,6 +104,55 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }); }, + async selectOptionFromComboBox(testTargetId: string, name: string) { + const target = await testSubjects.find(testTargetId, 1000); + await comboBox.openOptionsList(target); + await comboBox.setElement(target, name); + }, + + async configureQueryAnnotation(opts: { + queryString: string; + timeField: string; + textDecoration?: { type: 'none' | 'name' | 'field'; textField?: string }; + extraFields?: string[]; + }) { + // type * in the query editor + const queryInput = await testSubjects.find('lnsXY-annotation-query-based-query-input'); + await queryInput.type(opts.queryString); + await testSubjects.click('indexPattern-filters-existingFilterTrigger'); + await this.selectOptionFromComboBox( + 'lnsXY-annotation-query-based-field-picker', + opts.timeField + ); + if (opts.textDecoration) { + await testSubjects.click(`lnsXY_textVisibility_${opts.textDecoration.type}`); + if (opts.textDecoration.textField) { + await this.selectOptionFromComboBox( + 'lnsXY-annotation-query-based-text-decoration-field-picker', + opts.textDecoration.textField + ); + } + } + if (opts.extraFields) { + for (const field of opts.extraFields) { + await this.addFieldToTooltip(field); + } + } + }, + + async addFieldToTooltip(fieldName: string) { + const lastIndex = ( + await find.allByCssSelector('[data-test-subj^="lnsXY-annotation-tooltip-field-picker"]') + ).length; + await retry.try(async () => { + await testSubjects.click('lnsXY-annotation-tooltip-add_field'); + await this.selectOptionFromComboBox( + `lnsXY-annotation-tooltip-field-picker--${lastIndex}`, + fieldName + ); + }); + }, + /** * Changes the specified dimension to the specified operation and (optinally) field. * @@ -150,9 +199,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }); } if (opts.field) { - const target = await testSubjects.find('indexPattern-dimension-field'); - await comboBox.openOptionsList(target); - await comboBox.setElement(target, opts.field); + await this.selectOptionFromComboBox('indexPattern-dimension-field', opts.field); } if (opts.formula) { @@ -188,15 +235,17 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont isPreviousIncompatible?: boolean; }) { if (opts.operation) { - const target = await testSubjects.find('indexPattern-subFunction-selection-row'); - await comboBox.openOptionsList(target); - await comboBox.setElement(target, opts.operation); + await this.selectOptionFromComboBox( + 'indexPattern-subFunction-selection-row', + opts.operation + ); } if (opts.field) { - const target = await testSubjects.find('indexPattern-reference-field-selection-row'); - await comboBox.openOptionsList(target); - await comboBox.setElement(target, opts.field); + await this.selectOptionFromComboBox( + 'indexPattern-reference-field-selection-row', + opts.field + ); } }, @@ -588,10 +637,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont ).length; await retry.try(async () => { await testSubjects.click('indexPattern-terms-add-field'); - // count the number of defined terms - const target = await testSubjects.find(`indexPattern-dimension-field-${lastIndex}`, 1000); - await comboBox.openOptionsList(target); - await comboBox.setElement(target, field); + await this.selectOptionFromComboBox(`indexPattern-dimension-field-${lastIndex}`, field); }); }, @@ -608,7 +654,6 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }); // count the number of defined terms const target = await testSubjects.find(`indexPattern-dimension-field-${lastIndex}`); - // await comboBox.openOptionsList(target); for (const field of fields) { await comboBox.setCustom(`indexPattern-dimension-field-${lastIndex}`, field); await comboBox.openOptionsList(target); @@ -661,9 +706,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont await testSubjects.setValue('column-label-edit', label, { clearWithKeyboard: true }); }, async editDimensionFormat(format: string) { - const formatInput = await testSubjects.find('indexPattern-dimension-format'); - await comboBox.openOptionsList(formatInput); - await comboBox.setElement(formatInput, format); + await this.selectOptionFromComboBox('indexPattern-dimension-format', format); }, async editDimensionColor(color: string) { const colorPickerInput = await testSubjects.find('~indexPattern-dimension-colorPicker'); @@ -812,17 +855,15 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont * Returns the number of layers visible in the chart configuration */ async getLayerCount() { - const elements = await testSubjects.findAll('lnsLayerRemove'); - return elements.length; + return (await find.allByCssSelector(`[data-test-subj^="lns-layerPanel-"]`)).length; }, /** * Adds a new layer to the chart, fails if the chart does not support new layers */ - async createLayer(layerType: string = 'data') { + async createLayer(layerType: 'data' | 'referenceLine' | 'annotations' = 'data') { await testSubjects.click('lnsLayerAddButton'); - const layerCount = (await find.allByCssSelector(`[data-test-subj^="lns-layerPanel-"]`)) - .length; + const layerCount = await this.getLayerCount(); await retry.waitFor('check for layer type support', async () => { const fasterChecks = await Promise.all([ @@ -1257,9 +1298,9 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }, /** resets visualization/layer or removes a layer */ - async removeLayer() { + async removeLayer(index: number = 0) { await retry.try(async () => { - await testSubjects.click('lnsLayerRemove'); + await testSubjects.click(`lnsLayerRemove--${index}`); if (await testSubjects.exists('lnsLayerRemoveModal')) { await testSubjects.exists('lnsLayerRemoveConfirmButton'); await testSubjects.click('lnsLayerRemoveConfirmButton'); diff --git a/x-pack/test/functional/services/ml/navigation.ts b/x-pack/test/functional/services/ml/navigation.ts index 8261c3954b068..bc05cfd6a9bc3 100644 --- a/x-pack/test/functional/services/ml/navigation.ts +++ b/x-pack/test/functional/services/ml/navigation.ts @@ -102,6 +102,10 @@ export function MachineLearningNavigationProvider({ await this.assertTabEnabled('~mlMainTab & ~overview', expectedValue); }, + async assertNotificationsTabEnabled(expectedValue: boolean) { + await this.assertTabEnabled('~mlMainTab & ~notifications', expectedValue); + }, + async assertAnomalyDetectionTabEnabled(expectedValue: boolean) { await this.assertTabEnabled('~mlMainTab & ~anomalyDetection', expectedValue); }, @@ -146,6 +150,10 @@ export function MachineLearningNavigationProvider({ await this.navigateToArea('~mlMainTab & ~overview', 'mlPageOverview'); }, + async navigateToNotifications() { + await this.navigateToArea('~mlMainTab & ~notifications', 'mlPageNotifications'); + }, + async navigateToAnomalyDetection() { await this.navigateToArea('~mlMainTab & ~anomalyDetection', 'mlPageJobManagement'); }, diff --git a/x-pack/test/saved_object_tagging/functional/tests/dashboard_integration.ts b/x-pack/test/saved_object_tagging/functional/tests/dashboard_integration.ts index 2a31c0518798e..22c8b159b12ed 100644 --- a/x-pack/test/saved_object_tagging/functional/tests/dashboard_integration.ts +++ b/x-pack/test/saved_object_tagging/functional/tests/dashboard_integration.ts @@ -31,7 +31,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); } // click elsewhere to close the filter dropdown - const searchFilter = await find.byCssSelector('.euiPageBody .euiFieldSearch'); + const searchFilter = await find.byCssSelector('.euiPageTemplate .euiFieldSearch'); await searchFilter.click(); // wait until the table refreshes await listingTable.waitUntilTableIsLoaded(); diff --git a/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts b/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts index 13440b3d0cc74..97402fe5c76c7 100644 --- a/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts +++ b/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts @@ -30,7 +30,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); } // click elsewhere to close the filter dropdown - const searchFilter = await find.byCssSelector('.euiPageBody .euiFieldSearch'); + const searchFilter = await find.byCssSelector('.euiPageTemplate .euiFieldSearch'); await searchFilter.click(); }; diff --git a/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts b/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts index a1059331e3312..faf9e8aed8306 100644 --- a/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts +++ b/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts @@ -31,7 +31,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); } // click elsewhere to close the filter dropdown - const searchFilter = await find.byCssSelector('.euiPageBody .euiFieldSearch'); + const searchFilter = await find.byCssSelector('.euiPageTemplate .euiFieldSearch'); await searchFilter.click(); // wait until the table refreshes await listingTable.waitUntilTableIsLoaded(); diff --git a/x-pack/test/security_solution_cypress/config.ts b/x-pack/test/security_solution_cypress/config.ts index cb6ffd7d79f32..8d638789b61ff 100644 --- a/x-pack/test/security_solution_cypress/config.ts +++ b/x-pack/test/security_solution_cypress/config.ts @@ -49,9 +49,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { '--xpack.alerting.rules.minimumScheduleInterval.value=1s', '--xpack.ruleRegistry.unsafe.legacyMultiTenancy.enabled=true', `--xpack.securitySolution.enableExperimental=${JSON.stringify([ - 'riskyHostsEnabled', - 'riskyUsersEnabled', - 'entityAnalyticsDashboardEnabled', 'threatIntelligenceEnabled', ])}`, `--home.disableWelcomeScreen=true`, diff --git a/x-pack/test/security_solution_endpoint/page_objects/endpoint_responder.ts b/x-pack/test/security_solution_endpoint/page_objects/endpoint_responder.ts index 56a47631a4912..00062df861e6a 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/endpoint_responder.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/endpoint_responder.ts @@ -5,7 +5,7 @@ * 2.0. */ -import testSubjSelector from '@kbn/test-subj-selector'; +import { subj as testSubjSelector } from '@kbn/test-subj-selector'; import { FtrProviderContext } from '../ftr_provider_context'; const TEST_SUBJ = Object.freeze({ diff --git a/x-pack/test/security_solution_ftr/page_objects/timeline/index.ts b/x-pack/test/security_solution_ftr/page_objects/timeline/index.ts index e418f03143134..7675e6d8e8eea 100644 --- a/x-pack/test/security_solution_ftr/page_objects/timeline/index.ts +++ b/x-pack/test/security_solution_ftr/page_objects/timeline/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import testSubjSelector from '@kbn/test-subj-selector'; +import { subj as testSubjSelector } from '@kbn/test-subj-selector'; import { FtrService } from '../../../functional/ftr_provider_context'; import { DATE_RANGE_OPTION_TO_TEST_SUBJ_MAP } from '../helpers/super_date_picker'; diff --git a/x-pack/test/threat_intelligence_cypress/config.ts b/x-pack/test/threat_intelligence_cypress/config.ts index 43d80103c5a64..8d638789b61ff 100644 --- a/x-pack/test/threat_intelligence_cypress/config.ts +++ b/x-pack/test/threat_intelligence_cypress/config.ts @@ -49,8 +49,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { '--xpack.alerting.rules.minimumScheduleInterval.value=1s', '--xpack.ruleRegistry.unsafe.legacyMultiTenancy.enabled=true', `--xpack.securitySolution.enableExperimental=${JSON.stringify([ - 'riskyHostsEnabled', - 'riskyUsersEnabled', 'threatIntelligenceEnabled', ])}`, `--home.disableWelcomeScreen=true`, diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index e7684ba8b7905..7267b31905c95 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -50,6 +50,7 @@ { "path": "../../src/plugins/usage_collection/tsconfig.json" }, { "path": "../plugins/actions/tsconfig.json" }, { "path": "../plugins/alerting/tsconfig.json" }, + { "path": "../plugins/stack_connectors/tsconfig.json" }, { "path": "../plugins/apm/tsconfig.json" }, { "path": "../plugins/banners/tsconfig.json" }, { "path": "../plugins/cases/tsconfig.json" }, diff --git a/x-pack/test/usage_collection/test_suites/application_usage/index.ts b/x-pack/test/usage_collection/test_suites/application_usage/index.ts index 754ae98997c16..9311e554832e0 100644 --- a/x-pack/test/usage_collection/test_suites/application_usage/index.ts +++ b/x-pack/test/usage_collection/test_suites/application_usage/index.ts @@ -24,7 +24,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); } try { - expect(Object.keys(applicationUsageSchema).sort()).to.eql(appIds.sort()); + const enabledAppIds = Object.keys(applicationUsageSchema).filter( + // Profiling is currently disabled by default as it's in closed beta + (appId) => appId !== 'profiling' + ); + expect(enabledAppIds.sort()).to.eql(appIds.sort()); } catch (err) { err.message = `Application Usage's schema is not up-to-date with the actual registered apps. Please update it at src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts.\n${err.message}`; throw err; diff --git a/yarn.lock b/yarn.lock index eb8b7fbdf36df..ed8c1e12b5af6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2704,6 +2704,10 @@ version "0.0.0" uid "" +"@kbn/content-management-table-list@link:bazel-bin/packages/content-management/table_list": + version "0.0.0" + uid "" + "@kbn/core-analytics-browser-internal@link:bazel-bin/packages/core/analytics/core-analytics-browser-internal": version "0.0.0" uid "" @@ -6202,6 +6206,11 @@ resolved "https://registry.yarnpkg.com/@types/d3/-/d3-3.5.43.tgz#e9b4992817e0b6c5efaa7d6e5bb2cee4d73eab58" integrity sha512-t9ZmXOcpVxywRw86YtIC54g7M9puRh8hFedRvVfHKf5YyOP6pSxA0TvpXpfseXSCInoW4P7bggTrSDiUOs4g5w== +"@types/dagre@^0.7.47": + version "0.7.47" + resolved "https://registry.yarnpkg.com/@types/dagre/-/dagre-0.7.47.tgz#1d1b89e1fac36aaf5ef5ed6274bb123073095ac5" + integrity sha512-oX+3aRf7L6Cqq1MvbWmmD7FpAU/T8URwFFuHBagAiyHILn3i+RNZ35/tvyq28de+lZGY3W19BxJ7FeITQDO7aA== + "@types/dedent@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@types/dedent/-/dedent-0.7.0.tgz#155f339ca404e6dd90b9ce46a3f78fd69ca9b050" @@ -6294,6 +6303,11 @@ dependencies: "@types/jquery" "*" +"@types/fnv-plus@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@types/fnv-plus/-/fnv-plus-1.3.0.tgz#0f43f0b7e7b4b24de3a1cab69bfa009508f4c084" + integrity sha512-ijls8MsO6Q9JUSd5w1v4y2ijM6S4D/nmOyI/FwcepvrZfym0wZhLdYGFD5TJID7tga0O3I7SmtK69RzpSJ1Fcw== + "@types/fs-extra@^8.0.0": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.1.tgz#1e49f22d09aa46e19b51c0b013cb63d0d923a068" @@ -6707,6 +6721,10 @@ version "0.0.0" uid "" +"@types/kbn__content-management-table-list@link:bazel-bin/packages/content-management/table_list/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__core-analytics-browser-internal@link:bazel-bin/packages/core/analytics/core-analytics-browser-internal/npm_module_types": version "0.0.0" uid "" @@ -7751,6 +7769,10 @@ version "0.0.0" uid "" +"@types/kbn__test-subj-selector@link:bazel-bin/packages/kbn-test-subj-selector/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__test@link:bazel-bin/packages/kbn-test/npm_module_types": version "0.0.0" uid "" @@ -11769,10 +11791,10 @@ core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.9: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== -core-js@^3.0.4, core-js@^3.25.0, core-js@^3.6.5, core-js@^3.8.2, core-js@^3.8.3: - version "3.25.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.0.tgz#be71d9e0dd648ffd70c44a7ec2319d039357eceb" - integrity sha512-CVU1xvJEfJGhyCpBrzzzU1kjCfgsGUxhEvwUV2e/cOedYWHdmluamx+knDnmhqALddMG16fZvIqvs9aijsHHaA== +core-js@^3.0.4, core-js@^3.25.1, core-js@^3.6.5, core-js@^3.8.2, core-js@^3.8.3: + version "3.25.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.1.tgz#5818e09de0db8956e16bf10e2a7141e931b7c69c" + integrity sha512-sr0FY4lnO1hkQ4gLDr24K0DGnweGO1QwSj5BpfQjpSJPdqWalja4cTps29Y/PJVG/P7FYlPDkH3hO+Tr0CvDgQ== core-util-is@1.0.2, core-util-is@^1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -15002,6 +15024,11 @@ fn.name@1.x.x: resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== +fnv-plus@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/fnv-plus/-/fnv-plus-1.3.1.tgz#c34cb4572565434acb08ba257e4044ce2b006d67" + integrity sha512-Gz1EvfOneuFfk4yG458dJ3TLJ7gV19q3OM/vVvvHf7eT02Hm1DleB4edsia6ahbKgAYxO9gvyQ1ioWZR+a00Yw== + focus-lock@^0.11.2: version "0.11.2" resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.11.2.tgz#aeef3caf1cea757797ac8afdebaec8fd9ab243ed" @@ -22808,10 +22835,10 @@ react-helmet-async@^1.0.7: react-fast-compare "^3.2.0" shallowequal "^1.1.0" -react-hook-form@^7.34.2: - version "7.34.2" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.34.2.tgz#9ac6d1a309a7c4aaa369d1269357a70e9e9bf4de" - integrity sha512-1lYWbEqr0GW7HHUjMScXMidGvV0BE2RJV3ap2BL7G0EJirkqpccTaawbsvBO8GZaB3JjCeFBEbnEWI1P8ZoLRQ== +react-hook-form@^7.35.0: + version "7.35.0" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.35.0.tgz#b133de48fc84b1e62f9277ba79dfbacd9bb13dd3" + integrity sha512-9CYdOed+Itbiu5VMVxW0PK9mBR3f0gDGJcZEyUSm0eJbDymQ913TRs2gHcQZZmfTC+rtxyDFRuelMxx/+xwMcw== react-input-autosize@^3.0.0: version "3.0.0"