diff --git a/.buildkite/ftr_oblt_serverless_configs.yml b/.buildkite/ftr_oblt_serverless_configs.yml index fbf0406f37be4..75909e7c21c46 100644 --- a/.buildkite/ftr_oblt_serverless_configs.yml +++ b/.buildkite/ftr_oblt_serverless_configs.yml @@ -6,6 +6,10 @@ disabled: - x-pack/test_serverless/functional/test_suites/observability/cypress/config_headless.ts - x-pack/test_serverless/functional/test_suites/observability/cypress/config_runner.ts + # serverless config files that run deployment-agnostic tests + # Failing https://github.com/elastic/kibana/issues/195811 + - x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts + defaultQueue: 'n2-4-spot' enabled: - x-pack/test_serverless/api_integration/test_suites/observability/config.ts @@ -25,5 +29,3 @@ enabled: - x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group5.ts - x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group6.ts - x-pack/test_serverless/functional/test_suites/observability/config.screenshots.ts - # serverless config files that run deployment-agnostic tests - - x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts diff --git a/.buildkite/ftr_platform_stateful_configs.yml b/.buildkite/ftr_platform_stateful_configs.yml index d8a538f82779e..092cdb12a19f6 100644 --- a/.buildkite/ftr_platform_stateful_configs.yml +++ b/.buildkite/ftr_platform_stateful_configs.yml @@ -34,7 +34,9 @@ disabled: # Cypress configs, for now these are still run manually - x-pack/test/fleet_cypress/cli_config.ts + - x-pack/test/fleet_cypress/cli_config.space_awareness.ts - x-pack/test/fleet_cypress/config.ts + - x-pack/test/fleet_cypress/config.space_awareness.ts - x-pack/test/fleet_cypress/visual_config.ts defaultQueue: 'n2-4-spot' diff --git a/.buildkite/ftr_search_serverless_configs.yml b/.buildkite/ftr_search_serverless_configs.yml index e6efee5860806..413558bffa0fe 100644 --- a/.buildkite/ftr_search_serverless_configs.yml +++ b/.buildkite/ftr_search_serverless_configs.yml @@ -1,6 +1,10 @@ disabled: # Base config files, only necessary to inform config finding script + # serverless config files that run deployment-agnostic tests + # Failing https://github.com/elastic/kibana/issues/195811 + - x-pack/test/api_integration/deployment_agnostic/configs/serverless/search.serverless.config.ts + defaultQueue: 'n2-4-spot' enabled: - x-pack/test_serverless/api_integration/test_suites/search/config.ts @@ -18,5 +22,3 @@ enabled: - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group4.ts - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group5.ts - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group6.ts - # serverless config files that run deployment-agnostic tests - - x-pack/test/api_integration/deployment_agnostic/configs/serverless/search.serverless.config.ts diff --git a/.buildkite/ftr_security_serverless_configs.yml b/.buildkite/ftr_security_serverless_configs.yml index 6d42c030b2d4f..caf9fcc5ac92a 100644 --- a/.buildkite/ftr_security_serverless_configs.yml +++ b/.buildkite/ftr_security_serverless_configs.yml @@ -20,6 +20,10 @@ disabled: - x-pack/test_serverless/functional/config.base.ts - x-pack/test_serverless/shared/config.base.ts + # serverless config files that run deployment-agnostic tests + # Failing https://github.com/elastic/kibana/issues/195811 + - x-pack/test/api_integration/deployment_agnostic/configs/serverless/security.serverless.config.ts + defaultQueue: 'n2-4-spot' enabled: - x-pack/test_serverless/api_integration/test_suites/security/config.ts @@ -100,5 +104,3 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_endpoint/configs/serverless.endpoint.config.ts - x-pack/test/security_solution_endpoint/configs/serverless.integrations.config.ts - # serverless config files that run deployment-agnostic tests - - x-pack/test/api_integration/deployment_agnostic/configs/serverless/security.serverless.config.ts diff --git a/.buildkite/pull_requests.json b/.buildkite/pull_requests.json index 1f45c01042888..614d45969cdd7 100644 --- a/.buildkite/pull_requests.json +++ b/.buildkite/pull_requests.json @@ -30,7 +30,8 @@ "^\\.backportrc\\.json$", "^nav-kibana-dev\\.docnav\\.json$", "^src/dev/prs/kibana_qa_pr_list\\.json$", - "^\\.buildkite/pull_requests\\.json$" + "^\\.buildkite/pull_requests\\.json$", + "^\\.devcontainer/" ], "always_require_ci_on_changed": [ "^docs/developer/plugin-list.asciidoc$", diff --git a/.buildkite/scripts/steps/cloud/build_and_deploy.sh b/.buildkite/scripts/steps/cloud/build_and_deploy.sh index 25e7d8fc631c9..220ab497aaf7b 100755 --- a/.buildkite/scripts/steps/cloud/build_and_deploy.sh +++ b/.buildkite/scripts/steps/cloud/build_and_deploy.sh @@ -51,7 +51,7 @@ fi if is_pr_with_label "ci:cloud-redeploy"; then echo "--- Shutdown Previous Deployment" CLOUD_DEPLOYMENT_ID=$(ecctl deployment list --output json | jq -r '.deployments[] | select(.name == "'$CLOUD_DEPLOYMENT_NAME'") | .id') - if [ -z "${CLOUD_DEPLOYMENT_ID}" ]; then + if [ -z "${CLOUD_DEPLOYMENT_ID}" ] || [ "${CLOUD_DEPLOYMENT_ID}" == "null" ]; then echo "No deployment to remove" else echo "Shutting down previous deployment..." diff --git a/.buildkite/scripts/steps/functional/fleet_cypress.sh b/.buildkite/scripts/steps/functional/fleet_cypress.sh index 43eb329f860b7..e050b73a91c3e 100755 --- a/.buildkite/scripts/steps/functional/fleet_cypress.sh +++ b/.buildkite/scripts/steps/functional/fleet_cypress.sh @@ -12,4 +12,4 @@ echo "--- Fleet Cypress tests (Chrome)" cd x-pack/plugins/fleet set +e -yarn cypress:run:reporter; status=$?; yarn junit:merge || :; exit $status +yarn cypress:run:reporter; status=$?; yarn cypress_space_awareness:run:reporter; space_status=$?; yarn junit:merge || :; [ "$status" -ne 0 ] && exit $status || [ "$space_status" -ne 0 ] && exit $space_status || exit 0 diff --git a/.buildkite/scripts/steps/serverless/deploy.sh b/.buildkite/scripts/steps/serverless/deploy.sh index d30723393dacd..cbbc6c6c664dd 100644 --- a/.buildkite/scripts/steps/serverless/deploy.sh +++ b/.buildkite/scripts/steps/serverless/deploy.sh @@ -56,7 +56,7 @@ deploy() { PROJECT_ID=$(jq -r '[.items[] | select(.name == "'$PROJECT_NAME'")] | .[0].id' $PROJECT_EXISTS_LOGS) if is_pr_with_label "ci:project-redeploy"; then - if [ -z "${PROJECT_ID}" ]; then + if [ -z "${PROJECT_ID}" ] || [ "${PROJECT_ID}" == "null" ]; then echo "No project to remove" else echo "Shutting down previous project..." diff --git a/.eslintrc.js b/.eslintrc.js index 797b84522df3f..f2e54b2d116ae 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -978,6 +978,7 @@ module.exports = { files: [ 'x-pack/plugins/observability_solution/**/!(*.stories.tsx|*.test.tsx|*.storybook_decorator.tsx|*.mock.tsx)', 'src/plugins/ai_assistant_management/**/!(*.stories.tsx|*.test.tsx|*.storybook_decorator.tsx|*.mock.tsx)', + 'x-pack/packages/observability/logs_overview/**/!(*.stories.tsx|*.test.tsx|*.storybook_decorator.tsx|*.mock.tsx)', ], rules: { '@kbn/i18n/strings_should_be_translated_with_i18n': 'warn', @@ -1814,9 +1815,23 @@ module.exports = { files: [ 'src/plugins/interactive_setup/**/*.{js,mjs,ts,tsx}', 'test/interactive_setup_api_integration/**/*.{js,mjs,ts,tsx}', + 'test/interactive_setup_functional/**/*.{js,mjs,ts,tsx}', + + 'packages/kbn-mock-idp-plugin/**/*.{js,mjs,ts,tsx}', + 'packages/kbn-mock-idp-utils/**/*.{js,mjs,ts,tsx}', + 'packages/kbn-security-hardening/**/*.{js,mjs,ts,tsx}', + 'packages/kbn-user-profile-components/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/encrypted_saved_objects/**/*.{js,mjs,ts,tsx}', + 'x-pack/test/encrypted_saved_objects_api_integration/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/security/**/*.{js,mjs,ts,tsx}', + 'x-pack/packages/security/**/*.{js,mjs,ts,tsx}', + 'x-pack/test/security_api_integration/**/*.{js,mjs,ts,tsx}', + 'x-pack/test/security_functional/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/spaces/**/*.{js,mjs,ts,tsx}', + 'x-pack/test/spaces_api_integration/**/*.{js,mjs,ts,tsx}', ], rules: { '@typescript-eslint/consistent-type-imports': 1, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9b3c46d065fe1..10496d5351ef6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,11 +6,11 @@ #### x-pack/test/alerting_api_integration/common/plugins/aad @elastic/response-ops -packages/kbn-ace @elastic/kibana-management x-pack/plugins/actions @elastic/response-ops x-pack/test/alerting_api_integration/common/plugins/actions_simulators @elastic/response-ops packages/kbn-actions-types @elastic/response-ops src/plugins/advanced_settings @elastic/appex-sharedux @elastic/kibana-management +x-pack/packages/kbn-ai-assistant @elastic/search-kibana src/plugins/ai_assistant_management/selection @elastic/obs-knowledge-team x-pack/packages/ml/aiops_change_point_detection @elastic/ml-ui x-pack/packages/ml/aiops_common @elastic/ml-ui @@ -652,6 +652,7 @@ x-pack/packages/observability/alerting_test_data @elastic/obs-ux-management-team x-pack/test/cases_api_integration/common/plugins/observability @elastic/response-ops x-pack/packages/observability/get_padded_alert_time_range_util @elastic/obs-ux-management-team x-pack/plugins/observability_solution/observability_logs_explorer @elastic/obs-ux-logs-team +x-pack/packages/observability/logs_overview @elastic/obs-ux-logs-team x-pack/plugins/observability_solution/observability_onboarding/e2e @elastic/obs-ux-logs-team @elastic/obs-ux-onboarding-team x-pack/plugins/observability_solution/observability_onboarding @elastic/obs-ux-logs-team @elastic/obs-ux-onboarding-team x-pack/plugins/observability_solution/observability @elastic/obs-ux-management-team diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d07f60cf09253..737eedabadfa0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -36,4 +36,6 @@ When forming the risk matrix, consider some of the following examples and how th ### For maintainers -- [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) +- [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels) +- [ ] This will appear in the **Release Notes** and follow the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) + diff --git a/.github/updatecli/values.d/ironbank.yml b/.github/updatecli/values.d/ironbank.yml new file mode 100644 index 0000000000000..fd1134eda376a --- /dev/null +++ b/.github/updatecli/values.d/ironbank.yml @@ -0,0 +1,2 @@ +config: + - path: src/dev/build/tasks/os_packages/docker_generator/templates/ironbank \ No newline at end of file diff --git a/.github/updatecli/values.d/scm.yml b/.github/updatecli/values.d/scm.yml new file mode 100644 index 0000000000000..34d902fb389d5 --- /dev/null +++ b/.github/updatecli/values.d/scm.yml @@ -0,0 +1,11 @@ +scm: + enabled: true + owner: elastic + repository: kibana + branch: main + commitusingapi: true + # begin updatecli-compose policy values + user: kibanamachine + email: 42973632+kibanamachine@users.noreply.github.com + # end updatecli-compose policy values + diff --git a/.github/updatecli/values.d/updatecli-compose.yml b/.github/updatecli/values.d/updatecli-compose.yml new file mode 100644 index 0000000000000..02df609f2a30c --- /dev/null +++ b/.github/updatecli/values.d/updatecli-compose.yml @@ -0,0 +1,3 @@ +spec: + files: + - "updatecli-compose.yaml" \ No newline at end of file diff --git a/.github/workflows/oblt-github-commands.yml b/.github/workflows/oblt-github-commands.yml index 443c0fa5f9071..48df40f3343d9 100644 --- a/.github/workflows/oblt-github-commands.yml +++ b/.github/workflows/oblt-github-commands.yml @@ -14,6 +14,7 @@ on: permissions: contents: read + issues: write pull-requests: write jobs: diff --git a/.github/workflows/updatecli-compose.yml b/.github/workflows/updatecli-compose.yml new file mode 100644 index 0000000000000..cbab42d3a63b1 --- /dev/null +++ b/.github/workflows/updatecli-compose.yml @@ -0,0 +1,38 @@ +--- +name: updatecli-compose + +on: + workflow_dispatch: + schedule: + - cron: '0 6 * * *' + +permissions: + contents: read + +jobs: + compose: + runs-on: ubuntu-latest + permissions: + contents: write + packages: read + pull-requests: write + steps: + - uses: actions/checkout@v4 + + - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: elastic/oblt-actions/updatecli/run@v1 + with: + command: --experimental compose diff + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: elastic/oblt-actions/updatecli/run@v1 + with: + command: --experimental compose apply + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/NOTICE.txt b/NOTICE.txt index 80d49de19e5db..bdd6a95e57b04 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -68,37 +68,6 @@ Author Tobias Koppers @sokra --- This product has relied on ASTExplorer that is licensed under MIT. ---- -This product includes code that is based on Ace editor, which was available -under a "BSD" license. - -Distributed under the BSD license: - -Copyright (c) 2010, Ajax.org B.V. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of Ajax.org B.V. nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - --- This product includes code that is based on flot-charts, which was available under a "MIT" license. diff --git a/api_docs/kbn_ace.devdocs.json b/api_docs/kbn_ace.devdocs.json deleted file mode 100644 index 31b9c39264e4d..0000000000000 --- a/api_docs/kbn_ace.devdocs.json +++ /dev/null @@ -1,210 +0,0 @@ -{ - "id": "@kbn/ace", - "client": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] - }, - "server": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] - }, - "common": { - "classes": [], - "functions": [ - { - "parentPluginId": "@kbn/ace", - "id": "def-common.addToRules", - "type": "Function", - "tags": [], - "label": "addToRules", - "description": [], - "signature": [ - "(otherRules: any, embedUnder: any) => void" - ], - "path": "packages/kbn-ace/src/ace/modes/lexer_rules/x_json_highlight_rules.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/ace", - "id": "def-common.addToRules.$1", - "type": "Any", - "tags": [], - "label": "otherRules", - "description": [], - "signature": [ - "any" - ], - "path": "packages/kbn-ace/src/ace/modes/lexer_rules/x_json_highlight_rules.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/ace", - "id": "def-common.addToRules.$2", - "type": "Any", - "tags": [], - "label": "embedUnder", - "description": [], - "signature": [ - "any" - ], - "path": "packages/kbn-ace/src/ace/modes/lexer_rules/x_json_highlight_rules.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/ace", - "id": "def-common.ElasticsearchSqlHighlightRules", - "type": "Function", - "tags": [], - "label": "ElasticsearchSqlHighlightRules", - "description": [], - "signature": [ - "(this: any) => void" - ], - "path": "packages/kbn-ace/src/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [], - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/ace", - "id": "def-common.installXJsonMode", - "type": "Function", - "tags": [], - "label": "installXJsonMode", - "description": [], - "signature": [ - "(editor: ", - "Editor", - ") => void" - ], - "path": "packages/kbn-ace/src/ace/modes/x_json/x_json.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/ace", - "id": "def-common.installXJsonMode.$1", - "type": "Object", - "tags": [], - "label": "editor", - "description": [], - "signature": [ - "Editor" - ], - "path": "packages/kbn-ace/src/ace/modes/x_json/x_json.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/ace", - "id": "def-common.ScriptHighlightRules", - "type": "Function", - "tags": [], - "label": "ScriptHighlightRules", - "description": [], - "signature": [ - "(this: any) => void" - ], - "path": "packages/kbn-ace/src/ace/modes/lexer_rules/script_highlight_rules.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/ace", - "id": "def-common.ScriptHighlightRules.$1", - "type": "Any", - "tags": [], - "label": "this", - "description": [], - "signature": [ - "any" - ], - "path": "packages/kbn-ace/src/ace/modes/lexer_rules/script_highlight_rules.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/ace", - "id": "def-common.XJsonHighlightRules", - "type": "Function", - "tags": [], - "label": "XJsonHighlightRules", - "description": [], - "signature": [ - "(this: any) => void" - ], - "path": "packages/kbn-ace/src/ace/modes/lexer_rules/x_json_highlight_rules.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/ace", - "id": "def-common.XJsonHighlightRules.$1", - "type": "Any", - "tags": [], - "label": "this", - "description": [], - "signature": [ - "any" - ], - "path": "packages/kbn-ace/src/ace/modes/lexer_rules/x_json_highlight_rules.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - } - ], - "interfaces": [], - "enums": [], - "misc": [ - { - "parentPluginId": "@kbn/ace", - "id": "def-common.XJsonMode", - "type": "Any", - "tags": [], - "label": "XJsonMode", - "description": [], - "signature": [ - "any" - ], - "path": "packages/kbn-ace/src/ace/modes/x_json/x_json.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - } - ], - "objects": [] - } -} \ No newline at end of file diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx deleted file mode 100644 index 64aba3c6788e8..0000000000000 --- a/api_docs/kbn_ace.mdx +++ /dev/null @@ -1,33 +0,0 @@ ---- -#### -#### This 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: kibKbnAcePluginApi -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: 2024-10-09 -tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] ---- -import kbnAceObj from './kbn_ace.devdocs.json'; - - - -Contact [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) for questions regarding this plugin. - -**Code health stats** - -| Public API count | Any count | Items lacking comments | Missing exports | -|-------------------|-----------|------------------------|-----------------| -| 11 | 5 | 11 | 0 | - -## Common - -### Functions - - -### Consts, variables and types - - diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index a5a2307c4d6db..959b02632bf07 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -242,7 +242,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Package name           | Maintaining team | Description | API Cnt | Any Cnt | Missing
comments | Missing
exports | |--------------|----------------|-----------|--------------|----------|---------------|--------| -| | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 11 | 5 | 11 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 14 | 0 | 14 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 36 | 0 | 0 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 2 | 0 | 0 | 0 | @@ -797,4 +796,3 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 9 | 0 | 4 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 1254 | 0 | 4 | 0 | | | [@elastic/security-detection-rule-management](https://github.com/orgs/elastic/teams/security-detection-rule-management) | - | 20 | 0 | 10 | 0 | - diff --git a/dev_docs/nav-kibana-dev.docnav.json b/dev_docs/nav-kibana-dev.docnav.json index 8b8cd64a44664..a7d696fc10574 100644 --- a/dev_docs/nav-kibana-dev.docnav.json +++ b/dev_docs/nav-kibana-dev.docnav.json @@ -278,6 +278,10 @@ { "id": "kibDevReactKibanaContext", "label": "Kibana React Contexts" + }, + { + "id": "kibDevDocsChromeRecentlyAccessed", + "label": "Recently Viewed" } ] }, diff --git a/dev_docs/shared_ux/chrome_recently_accessed/chrome_recently_accessed.mdx b/dev_docs/shared_ux/chrome_recently_accessed/chrome_recently_accessed.mdx new file mode 100644 index 0000000000000..cca466bcf1ac3 --- /dev/null +++ b/dev_docs/shared_ux/chrome_recently_accessed/chrome_recently_accessed.mdx @@ -0,0 +1,66 @@ +--- +id: kibDevDocsChromeRecentlyAccessed +slug: /kibana-dev-docs/chrome/recently-accessed +title: Chrome Recently Viewed +description: How to use chrome's recently accessed service to add your links to the recently viewed list in the side navigation. +date: 2024-10-04 +tags: ['kibana', 'dev', 'contributor', 'chrome', 'navigation', 'shared-ux'] +--- + +## Introduction + +The service allows applications to register recently visited objects. These items are displayed in the "Recently Viewed" section of a side navigation menu, providing users with quick access to their previously visited resources. This service includes methods for adding, retrieving, and subscribing to the recently accessed history. + +![Recently viewed section in the sidenav](./chrome_recently_accessed.png) + +## Guidelines + +The service should be used thoughtfully to provide users with easy access to key resources they've interacted with. Unlike browser history, this feature is for important items that users may want to revisit. + +### DOs + +- Register important resources that users may want to revisit. Like a dashboard, a saved search, or another specific object. +- Update the link when the state of the current resource changes. For example, if a user changes the time range while on a dashboard, update the recently viewed link to reflect the latest viewed state where possible. See below for instructions on how to update the link when state changes. + +### DON'Ts + +- Don't register every page view. +- Don't register temporary or transient states as individual items. +- Prevent overloading. Keep the list focused on high-value resources. +- Don't add a recently viewed object without first speaking to relevant Product Managers. + +## Usage + +To register an item with the `ChromeRecentlyAccessed` service, provide a unique `id`, a `label`, and a `link`. The `id` is used to identify and deduplicate the item, the `label` is displayed in the "Recently Viewed" list and the `link` is used to navigate to the item when selected. + +```ts +const link = '/app/map/1234'; +const label = 'Map 1234'; +const id = 'map-1234'; + +coreStart.chrome.recentlyAccessed.add(link, label, id); +``` + +To update the link when state changes, add another item with the same `id`. This will replace the existing item in the "Recently Viewed" list. + +```ts +const link = '/app/map/1234'; +const label = 'Map 1234'; + +coreStart.chrome.recentlyAccessed.add(`/app/map/1234`, label, id); + +// User changes the time range and we want to update the link in the "Recently Viewed" list +coreStart.chrome.recentlyAccessed.add( + `/app/map/1234?timeRangeFrom=now-30m&timeRangeTo=now`, + label, + id +); +``` + +## Implementation details + +The services is based on package. This package provides a `RecentlyAccessedService` that uses browser local storage to manage records of recently accessed objects. Internally it implements the queue with a maximum length of 20 items. When the queue is full, the oldest item is removed. +Applications can create their own instance of `RecentlyAccessedService` to manage their own list of recently accessed items scoped to their application. + +- is a service available via `coreStart.chrome.recentlyAccessed` and should be used to add items to chrome's sidenav. +- is package that `ChromeRecentlyAccessed` is using internally and the package can be used to create your own instance and manage your own list of recently accessed items that is independent for chrome's sidenav. \ No newline at end of file diff --git a/dev_docs/shared_ux/chrome_recently_accessed/chrome_recently_accessed.png b/dev_docs/shared_ux/chrome_recently_accessed/chrome_recently_accessed.png new file mode 100644 index 0000000000000..41d3913b048a2 Binary files /dev/null and b/dev_docs/shared_ux/chrome_recently_accessed/chrome_recently_accessed.png differ diff --git a/dev_docs/shared_ux/shared_ux_landing.mdx b/dev_docs/shared_ux/shared_ux_landing.mdx index 4be8ad134be15..d96798eefa61f 100644 --- a/dev_docs/shared_ux/shared_ux_landing.mdx +++ b/dev_docs/shared_ux/shared_ux_landing.mdx @@ -66,5 +66,10 @@ layout: landing title: 'Kibana React Contexts', description: 'Learn how to use common React contexts in Kibana', }, + { + pageId: 'kibDevDocsChromeRecentlyAccessed', + title: 'Chrome Recently Viewed', + description: 'Learn how to add recently viewed items to the side navigation', + }, ]} /> diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index 50095f8b7018f..0b97a425001ec 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -41,7 +41,6 @@ yarn kbn watch [discrete] === List of Already Migrated Packages to Bazel -- @kbn/ace - @kbn/analytics - @kbn/apm-config-loader - @kbn/apm-utils @@ -93,4 +92,4 @@ yarn kbn watch - @kbn/ui-shared-deps-npm - @kbn/ui-shared-deps-src - @kbn/utility-types -- @kbn/utils +- @kbn/utils \ No newline at end of file diff --git a/docs/maps/connect-to-ems.asciidoc b/docs/maps/connect-to-ems.asciidoc index e41d544d64e4d..1ccdedb1da2a9 100644 --- a/docs/maps/connect-to-ems.asciidoc +++ b/docs/maps/connect-to-ems.asciidoc @@ -1,6 +1,6 @@ :ems: Elastic Maps Service :ems-docker-repo: docker.elastic.co/elastic-maps-service/elastic-maps-server -:ems-docker-image: {ems-docker-repo}:{version}-amd64 +:ems-docker-image: {ems-docker-repo}:{version} :ems-headers-url: https://deployment-host [[maps-connect-to-ems]] @@ -81,34 +81,53 @@ If you cannot connect to {ems} from the {kib} server or browser clients, and you {hosted-ems} is a self-managed version of {ems} offered as a Docker image that provides both the EMS basemaps and EMS boundaries. The image is bundled with basemaps up to zoom level 8. After connecting it to your {es} cluster for license validation, you have the option to download and configure a more detailed basemaps database. -You can use +docker pull+ to download the {hosted-ems} image from the Elastic Docker registry. - +. Pull the {hosted-ems} Docker image. ++ ifeval::["{release-state}"=="unreleased"] -Version {version} of {hosted-ems} has not yet been released, so no Docker image is currently available for this version. +WARNING: Version {version} of {hosted-ems} has not yet been released. +No Docker image is currently available for this version. endif::[] - -ifeval::["{release-state}"!="unreleased"] - ++ ["source","bash",subs="attributes"] ---------------------------------- docker pull {ems-docker-image} ---------------------------------- -Start {hosted-ems} and expose the default port `8080`: +. Optional: Install +https://docs.sigstore.dev/system_config/installation/[Cosign] for your +environment. Then use Cosign to verify the {es} image's signature. ++ +[source,sh,subs="attributes"] +---- +wget https://artifacts.elastic.co/cosign.pub +cosign verify --key cosign.pub {ems-docker-image} +---- ++ +The `cosign` command prints the check results and the signature payload in JSON format: ++ +[source,sh,subs="attributes"] +-------------------------------------------- +Verification for {ems-docker-image} -- +The following checks were performed on each of these signatures: + - The cosign claims were validated + - Existence of the claims in the transparency log was verified offline + - The signatures were verified against the specified public key +-------------------------------------------- + +. Start {hosted-ems} and expose the default port `8080`: ++ ["source","bash",subs="attributes"] ---------------------------------- docker run --rm --init --publish 8080:8080 \ {ems-docker-image} ---------------------------------- - ++ Once {hosted-ems} is running, follow instructions from the webpage at `localhost:8080` to define a configuration file and optionally download a more detailed basemaps database. - ++ [role="screenshot"] image::images/elastic-maps-server-instructions.png[Set-up instructions] -endif::[] - [float] [[elastic-maps-server-configuration]] ==== Configuration @@ -193,7 +212,6 @@ One way to configure {hosted-ems} is to provide `elastic-maps-server.yml` via bi ["source","yaml",subs="attributes"] -------------------------------------------- -version: '2' services: ems-server: image: {ems-docker-image} @@ -212,7 +230,6 @@ These variables can be set with +docker-compose+ like this: ["source","yaml",subs="attributes"] ---------------------------------------------------------- -version: '2' services: ems-server: image: {ems-docker-image} diff --git a/docs/maps/images/elastic-maps-server-instructions.png b/docs/maps/images/elastic-maps-server-instructions.png index 5c0b47ce8f49f..524ae2192b5e5 100644 Binary files a/docs/maps/images/elastic-maps-server-instructions.png and b/docs/maps/images/elastic-maps-server-instructions.png differ diff --git a/package.json b/package.json index 57b84f1c46dcb..734ce9cce5128 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.cd77847.0", "@types/react": "~18.2.0", "@types/react-dom": "~18.2.0", + "@xstate5/react/**/xstate": "^5.18.1", "globby/fast-glob": "^3.2.11" }, "dependencies": { @@ -153,11 +154,11 @@ "@hapi/wreck": "^18.1.0", "@hello-pangea/dnd": "16.6.0", "@kbn/aad-fixtures-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/aad", - "@kbn/ace": "link:packages/kbn-ace", "@kbn/actions-plugin": "link:x-pack/plugins/actions", "@kbn/actions-simulators-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/actions_simulators", "@kbn/actions-types": "link:packages/kbn-actions-types", "@kbn/advanced-settings-plugin": "link:src/plugins/advanced_settings", + "@kbn/ai-assistant": "link:x-pack/packages/kbn-ai-assistant", "@kbn/ai-assistant-management-plugin": "link:src/plugins/ai_assistant_management/selection", "@kbn/aiops-change-point-detection": "link:x-pack/packages/ml/aiops_change_point_detection", "@kbn/aiops-common": "link:x-pack/packages/ml/aiops_common", @@ -687,6 +688,7 @@ "@kbn/observability-fixtures-plugin": "link:x-pack/test/cases_api_integration/common/plugins/observability", "@kbn/observability-get-padded-alert-time-range-util": "link:x-pack/packages/observability/get_padded_alert_time_range_util", "@kbn/observability-logs-explorer-plugin": "link:x-pack/plugins/observability_solution/observability_logs_explorer", + "@kbn/observability-logs-overview": "link:x-pack/packages/observability/logs_overview", "@kbn/observability-onboarding-plugin": "link:x-pack/plugins/observability_solution/observability_onboarding", "@kbn/observability-plugin": "link:x-pack/plugins/observability_solution/observability", "@kbn/observability-shared-plugin": "link:x-pack/plugins/observability_solution/observability_shared", @@ -1050,6 +1052,7 @@ "@turf/helpers": "6.0.1", "@turf/length": "^6.0.2", "@xstate/react": "^3.2.2", + "@xstate5/react": "npm:@xstate/react@^4.1.2", "adm-zip": "^0.5.9", "ai": "^2.2.33", "ajv": "^8.12.0", @@ -1063,7 +1066,6 @@ "bitmap-sdf": "^1.0.3", "blurhash": "^2.0.1", "borc": "3.0.0", - "brace": "0.11.1", "brok": "^6.0.0", "byte-size": "^8.1.0", "cacheable-lookup": "6", @@ -1201,7 +1203,6 @@ "re-resizable": "^6.9.9", "re2js": "0.4.2", "react": "^17.0.2", - "react-ace": "^7.0.5", "react-diff-view": "^3.2.1", "react-dom": "^17.0.2", "react-dropzone": "^4.2.9", @@ -1283,6 +1284,7 @@ "whatwg-fetch": "^3.0.0", "xml2js": "^0.5.0", "xstate": "^4.38.2", + "xstate5": "npm:xstate@^5.18.1", "xterm": "^5.1.0", "yauzl": "^2.10.0", "yazl": "^2.5.1", @@ -1304,6 +1306,7 @@ "@babel/plugin-proposal-optional-chaining": "^7.21.0", "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-env": "^7.24.7", diff --git a/packages/kbn-ace/README.md b/packages/kbn-ace/README.md deleted file mode 100644 index c11d5cc2f24b8..0000000000000 --- a/packages/kbn-ace/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# @kbn/ace - -This package contains the XJSON mode for brace. This is an extension of the `brace/mode/json` mode. - -This package also contains an import of the entire brace editor which is used for creating the custom XJSON worker. - -## Note to plugins -_This code should not be eagerly loaded_. - -Make sure imports of this package are behind a lazy-load `import()` statement. - -Your plugin should already be loading application code this way in the `mount` function. - -## Deprecated - -This package is considered deprecated and will be removed in future. - -New and existing editor functionality should use Monaco. - -_Do not add new functionality to this package_. Build new functionality for Monaco and use it instead. diff --git a/packages/kbn-ace/index.ts b/packages/kbn-ace/index.ts deleted file mode 100644 index c9cc0b7a73e86..0000000000000 --- a/packages/kbn-ace/index.ts +++ /dev/null @@ -1,17 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -export { - ElasticsearchSqlHighlightRules, - ScriptHighlightRules, - XJsonHighlightRules, - addXJsonToRules, - XJsonMode, - installXJsonMode, -} from './src/ace/modes'; diff --git a/packages/kbn-ace/kibana.jsonc b/packages/kbn-ace/kibana.jsonc deleted file mode 100644 index 0a01d96a6b1c6..0000000000000 --- a/packages/kbn-ace/kibana.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/ace", - "owner": "@elastic/kibana-management" -} diff --git a/packages/kbn-ace/package.json b/packages/kbn-ace/package.json deleted file mode 100644 index 3d3ed36941978..0000000000000 --- a/packages/kbn-ace/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@kbn/ace", - "version": "1.0.0", - "private": true, - "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0" -} \ No newline at end of file diff --git a/packages/kbn-ace/src/ace/modes/index.ts b/packages/kbn-ace/src/ace/modes/index.ts deleted file mode 100644 index ffbb385663e48..0000000000000 --- a/packages/kbn-ace/src/ace/modes/index.ts +++ /dev/null @@ -1,17 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -export { - ElasticsearchSqlHighlightRules, - ScriptHighlightRules, - XJsonHighlightRules, - addXJsonToRules, -} from './lexer_rules'; - -export { installXJsonMode, XJsonMode } from './x_json'; diff --git a/packages/kbn-ace/src/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts b/packages/kbn-ace/src/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts deleted file mode 100644 index a4cb60529281d..0000000000000 --- a/packages/kbn-ace/src/ace/modes/lexer_rules/elasticsearch_sql_highlight_rules.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import ace from 'brace'; - -const { TextHighlightRules } = ace.acequire('ace/mode/text_highlight_rules'); -const oop = ace.acequire('ace/lib/oop'); - -export const ElasticsearchSqlHighlightRules = function (this: any) { - // See https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-commands.html - const keywords = - 'describe|between|in|like|not|and|or|desc|select|from|where|having|group|by|order' + - 'asc|desc|pivot|for|in|as|show|columns|include|frozen|tables|escape|limit|rlike|all|distinct|is'; - - const builtinConstants = 'true|false'; - - // See https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-syntax-show-functions.html - const builtinFunctions = - 'avg|count|first|first_value|last|last_value|max|min|sum|kurtosis|mad|percentile|percentile_rank|skewness' + - '|stddev_pop|sum_of_squares|var_pop|histogram|case|coalesce|greatest|ifnull|iif|isnull|least|nullif|nvl' + - '|curdate|current_date|current_time|current_timestamp|curtime|dateadd|datediff|datepart|datetrunc|date_add' + - '|date_diff|date_part|date_trunc|day|dayname|dayofmonth|dayofweek|dayofyear|day_name|day_of_month|day_of_week' + - '|day_of_year|dom|dow|doy|hour|hour_of_day|idow|isodayofweek|isodow|isoweek|isoweekofyear|iso_day_of_week|iso_week_of_year' + - '|iw|iwoy|minute|minute_of_day|minute_of_hour|month|monthname|month_name|month_of_year|now|quarter|second|second_of_minute' + - '|timestampadd|timestampdiff|timestamp_add|timestamp_diff|today|week|week_of_year|year|abs|acos|asin|atan|atan2|cbrt' + - '|ceil|ceiling|cos|cosh|cot|degrees|e|exp|expm1|floor|log|log10|mod|pi|power|radians|rand|random|round|sign|signum|sin' + - '|sinh|sqrt|tan|truncate|ascii|bit_length|char|character_length|char_length|concat|insert|lcase|left|length|locate' + - '|ltrim|octet_length|position|repeat|replace|right|rtrim|space|substring|ucase|cast|convert|database|user|st_astext|st_aswkt' + - '|st_distance|st_geometrytype|st_geomfromtext|st_wkttosql|st_x|st_y|st_z|score'; - - // See https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-data-types.html - const dataTypes = - 'null|boolean|byte|short|integer|long|double|float|half_float|scaled_float|keyword|text|binary|date|ip|object|nested|time' + - '|interval_year|interval_month|interval_day|interval_hour|interval_minute|interval_second|interval_year_to_month' + - 'inteval_day_to_hour|interval_day_to_minute|interval_day_to_second|interval_hour_to_minute|interval_hour_to_second' + - 'interval_minute_to_second|geo_point|geo_shape|shape'; - - const keywordMapper = this.createKeywordMapper( - { - keyword: [keywords, builtinFunctions, builtinConstants, dataTypes].join('|'), - }, - 'identifier', - true - ); - - this.$rules = { - start: [ - { - token: 'comment', - regex: '--.*$', - }, - { - token: 'comment', - start: '/\\*', - end: '\\*/', - }, - { - token: 'string', // " string - regex: '".*?"', - }, - { - token: 'constant', // ' string - regex: "'.*?'", - }, - { - token: 'string', // ` string (apache drill) - regex: '`.*?`', - }, - { - token: 'entity.name.function', // float - regex: '[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b', - }, - { - token: keywordMapper, - regex: '[a-zA-Z_$][a-zA-Z0-9_$]*\\b', - }, - { - token: 'keyword.operator', - regex: '⇐|<⇒|\\*|\\.|\\:\\:|\\+|\\-|\\/|\\/\\/|%|&|\\^|~|<|>|<=|=>|==|!=|<>|=', - }, - { - token: 'paren.lparen', - regex: '[\\(]', - }, - { - token: 'paren.rparen', - regex: '[\\)]', - }, - { - token: 'text', - regex: '\\s+', - }, - ], - }; - this.normalizeRules(); -}; - -oop.inherits(ElasticsearchSqlHighlightRules, TextHighlightRules); diff --git a/packages/kbn-ace/src/ace/modes/lexer_rules/index.ts b/packages/kbn-ace/src/ace/modes/lexer_rules/index.ts deleted file mode 100644 index aa8c6af19c10f..0000000000000 --- a/packages/kbn-ace/src/ace/modes/lexer_rules/index.ts +++ /dev/null @@ -1,12 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -export { ElasticsearchSqlHighlightRules } from './elasticsearch_sql_highlight_rules'; -export { ScriptHighlightRules } from './script_highlight_rules'; -export { XJsonHighlightRules, addToRules as addXJsonToRules } from './x_json_highlight_rules'; diff --git a/packages/kbn-ace/src/ace/modes/lexer_rules/script_highlight_rules.ts b/packages/kbn-ace/src/ace/modes/lexer_rules/script_highlight_rules.ts deleted file mode 100644 index 64e8a1a6594bd..0000000000000 --- a/packages/kbn-ace/src/ace/modes/lexer_rules/script_highlight_rules.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import ace from 'brace'; -const oop = ace.acequire('ace/lib/oop'); -const { TextHighlightRules } = ace.acequire('ace/mode/text_highlight_rules'); -const painlessKeywords = - 'def|int|long|byte|String|float|double|char|null|if|else|while|do|for|continue|break|new|try|catch|throw|this|instanceof|return|ctx'; - -export function ScriptHighlightRules(this: any) { - this.name = 'ScriptHighlightRules'; - this.$rules = { - start: [ - { - token: 'script.comment', - regex: '\\/\\/.*$', - }, - { - token: 'script.string.regexp', - regex: '[/](?:(?:\\[(?:\\\\]|[^\\]])+\\])|(?:\\\\/|[^\\]/]))*[/]\\w*\\s*(?=[).,;]|$)', - }, - { - token: 'script.string', // single line - regex: "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']", - }, - { - token: 'script.constant.numeric', // hex - regex: '0[xX][0-9a-fA-F]+\\b', - }, - { - token: 'script.constant.numeric', // float - regex: '[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b', - }, - { - token: 'script.constant.language.boolean', - regex: '(?:true|false)\\b', - }, - { - token: 'script.keyword', - regex: painlessKeywords, - }, - { - token: 'script.text', - regex: '[a-zA-Z_$][a-zA-Z0-9_$]*\\b', - }, - { - token: 'script.keyword.operator', - regex: - '\\?\\.|\\*\\.|=~|==~|!|%|&|\\*|\\-\\-|\\-|\\+\\+|\\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|->|!|&&|\\|\\||\\?\\:|\\*=|%=|\\+=|\\-=|&=|\\^=|\\b(?:in|instanceof|new|typeof|void)', - }, - { - token: 'script.lparen', - regex: '[[({]', - }, - { - token: 'script.rparen', - regex: '[\\])}]', - }, - { - token: 'script.text', - regex: '\\s+', - }, - ], - }; -} - -oop.inherits(ScriptHighlightRules, TextHighlightRules); diff --git a/packages/kbn-ace/src/ace/modes/lexer_rules/x_json_highlight_rules.ts b/packages/kbn-ace/src/ace/modes/lexer_rules/x_json_highlight_rules.ts deleted file mode 100644 index f69e2fbbf5d8a..0000000000000 --- a/packages/kbn-ace/src/ace/modes/lexer_rules/x_json_highlight_rules.ts +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { defaultsDeep } from 'lodash'; -import ace from 'brace'; -import 'brace/mode/json'; - -import { ElasticsearchSqlHighlightRules } from './elasticsearch_sql_highlight_rules'; -import { ScriptHighlightRules } from './script_highlight_rules'; - -const { JsonHighlightRules } = ace.acequire('ace/mode/json_highlight_rules'); -const oop = ace.acequire('ace/lib/oop'); - -const jsonRules = function (root: any) { - root = root ? root : 'json'; - const rules: any = {}; - const xJsonRules = [ - { - token: [ - 'variable', - 'whitespace', - 'ace.punctuation.colon', - 'whitespace', - 'punctuation.start_triple_quote', - ], - regex: '("(?:[^"]*_)?script"|"inline"|"source")(\\s*?)(:)(\\s*?)(""")', - next: 'script-start', - merge: false, - push: true, - }, - { - token: 'variable', // single line - regex: '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]\\s*(?=:)', - }, - { - token: 'punctuation.start_triple_quote', - regex: '"""', - next: 'string_literal', - merge: false, - push: true, - }, - { - token: 'string', // single line - regex: '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]', - }, - { - token: 'constant.numeric', // hex - regex: '0[xX][0-9a-fA-F]+\\b', - }, - { - token: 'constant.numeric', // float - regex: '[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b', - }, - { - token: 'constant.language.boolean', - regex: '(?:true|false)\\b', - }, - { - token: 'invalid.illegal', // single quoted strings are not allowed - regex: "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']", - }, - { - token: 'invalid.illegal', // comments are not allowed - regex: '\\/\\/.*$', - }, - { - token: 'paren.lparen', - merge: false, - regex: '{', - next: root, - push: true, - }, - { - token: 'paren.lparen', - merge: false, - regex: '[[(]', - }, - { - token: 'paren.rparen', - merge: false, - regex: '[\\])]', - }, - { - token: 'paren.rparen', - regex: '}', - merge: false, - next: 'pop', - }, - { - token: 'punctuation.comma', - regex: ',', - }, - { - token: 'punctuation.colon', - regex: ':', - }, - { - token: 'whitespace', - regex: '\\s+', - }, - { - token: 'text', - regex: '.+?', - }, - ]; - - rules[root] = xJsonRules; - rules[root + '-sql'] = [ - { - token: [ - 'variable', - 'whitespace', - 'ace.punctuation.colon', - 'whitespace', - 'punctuation.start_triple_quote', - ], - regex: '("query")(\\s*?)(:)(\\s*?)(""")', - next: 'sql-start', - merge: false, - push: true, - }, - ].concat(xJsonRules as any); - - rules.string_literal = [ - { - token: 'punctuation.end_triple_quote', - regex: '"""', - next: 'pop', - }, - { - token: 'multi_string', - regex: '.', - }, - ]; - return rules; -}; - -export function XJsonHighlightRules(this: any) { - this.$rules = { - ...jsonRules('start'), - }; - - this.embedRules(ScriptHighlightRules, 'script-', [ - { - token: 'punctuation.end_triple_quote', - regex: '"""', - next: 'pop', - }, - ]); - - this.embedRules(ElasticsearchSqlHighlightRules, 'sql-', [ - { - token: 'punctuation.end_triple_quote', - regex: '"""', - next: 'pop', - }, - ]); -} - -oop.inherits(XJsonHighlightRules, JsonHighlightRules); - -export function addToRules(otherRules: any, embedUnder: any) { - otherRules.$rules = defaultsDeep(otherRules.$rules, jsonRules(embedUnder)); - otherRules.embedRules(ScriptHighlightRules, 'script-', [ - { - token: 'punctuation.end_triple_quote', - regex: '"""', - next: 'pop', - }, - ]); - otherRules.embedRules(ElasticsearchSqlHighlightRules, 'sql-', [ - { - token: 'punctuation.end_triple_quote', - regex: '"""', - next: 'pop', - }, - ]); -} diff --git a/packages/kbn-ace/src/ace/modes/x_json/index.ts b/packages/kbn-ace/src/ace/modes/x_json/index.ts deleted file mode 100644 index a1651c9e06979..0000000000000 --- a/packages/kbn-ace/src/ace/modes/x_json/index.ts +++ /dev/null @@ -1,10 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -export { installXJsonMode, XJsonMode } from './x_json'; diff --git a/packages/kbn-ace/src/ace/modes/x_json/worker/index.ts b/packages/kbn-ace/src/ace/modes/x_json/worker/index.ts deleted file mode 100644 index b09099ed9ad01..0000000000000 --- a/packages/kbn-ace/src/ace/modes/x_json/worker/index.ts +++ /dev/null @@ -1,16 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -// @ts-ignore -import src from '!!raw-loader!./x_json.ace.worker'; - -export const workerModule = { - id: 'ace/mode/json_worker', - src, -}; diff --git a/packages/kbn-ace/src/ace/modes/x_json/worker/worker.d.ts b/packages/kbn-ace/src/ace/modes/x_json/worker/worker.d.ts deleted file mode 100644 index 34598ea61003b..0000000000000 --- a/packages/kbn-ace/src/ace/modes/x_json/worker/worker.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -// Satisfy TS's requirements that the module be declared per './index.ts'. -declare module '!!raw-loader!./worker.js' { - const content: string; - // eslint-disable-next-line import/no-default-export - export default content; -} diff --git a/packages/kbn-ace/src/ace/modes/x_json/worker/x_json.ace.worker.js b/packages/kbn-ace/src/ace/modes/x_json/worker/x_json.ace.worker.js deleted file mode 100644 index 63ca258e524d4..0000000000000 --- a/packages/kbn-ace/src/ace/modes/x_json/worker/x_json.ace.worker.js +++ /dev/null @@ -1,1265 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -/* @notice - * - * This product includes code that is based on Ace editor, which was available - * under a "BSD" license. - * - * Distributed under the BSD license: - * - * Copyright (c) 2010, Ajax.org B.V. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Ajax.org B.V. nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* eslint-disable prettier/prettier,no-var,eqeqeq,no-use-before-define,block-scoped-var,no-undef, - guard-for-in,one-var,strict,no-redeclare,no-sequences,no-proto,new-cap,no-nested-ternary,no-unused-vars, - prefer-const,no-empty,no-extend-native,camelcase */ -/* - This file is loaded up as a blob by Brace to hand to Ace to load as Jsonp - (hence the redefining of everything). It is based on the json - mode from the brace distro. - - It is very likely that this file will be removed in future but for now it enables - extended JSON parsing, like e.g. """{}""" (triple quotes) -*/ -// @internal -// @ts-nocheck -"no use strict"; -! function(window) { - function resolveModuleId(id, paths) { - for (var testPath = id, tail = ""; testPath;) { - var alias = paths[testPath]; - if ("string" == typeof alias) return alias + tail; - if (alias) return alias.location.replace(/\/*$/, "/") + (tail || alias.main || alias.name); - if (alias === !1) return ""; - var i = testPath.lastIndexOf("/"); - if (-1 === i) break; - tail = testPath.substr(i) + tail, testPath = testPath.slice(0, i) - } - return id - } - if (!(void 0 !== window.window && window.document || window.acequire && window.define)) { - window.console || (window.console = function() { - var msgs = Array.prototype.slice.call(arguments, 0); - postMessage({ - type: "log", - data: msgs - }) - }, window.console.error = window.console.warn = window.console.log = window.console.trace = window.console), window.window = window, window.ace = window, window.onerror = function(message, file, line, col, err) { - postMessage({ - type: "error", - data: { - message: message, - data: err.data, - file: file, - line: line, - col: col, - stack: err.stack - } - }) - }, window.normalizeModule = function(parentId, moduleName) { - if (-1 !== moduleName.indexOf("!")) { - var chunks = moduleName.split("!"); - return window.normalizeModule(parentId, chunks[0]) + "!" + window.normalizeModule(parentId, chunks[1]) - } - if ("." == moduleName.charAt(0)) { - var base = parentId.split("/").slice(0, -1).join("/"); - for (moduleName = (base ? base + "/" : "") + moduleName; - 1 !== moduleName.indexOf(".") && previous != moduleName;) { - var previous = moduleName; - moduleName = moduleName.replace(/^\.\//, "").replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, "") - } - } - return moduleName - }, window.acequire = function acequire(parentId, id) { - if (id || (id = parentId, parentId = null), !id.charAt) throw Error("worker.js acequire() accepts only (parentId, id) as arguments"); - id = window.normalizeModule(parentId, id); - var module = window.acequire.modules[id]; - if (module) return module.initialized || (module.initialized = !0, module.exports = module.factory().exports), module.exports; - if (!window.acequire.tlns) return console.log("unable to load " + id); - var path = resolveModuleId(id, window.acequire.tlns); - return ".js" != path.slice(-3) && (path += ".js"), window.acequire.id = id, window.acequire.modules[id] = {}, importScripts(path), window.acequire(parentId, id) - }, window.acequire.modules = {}, window.acequire.tlns = {}, window.define = function(id, deps, factory) { - if (2 == arguments.length ? (factory = deps, "string" != typeof id && (deps = id, id = window.acequire.id)) : 1 == arguments.length && (factory = id, deps = [], id = window.acequire.id), "function" != typeof factory) return window.acequire.modules[id] = { - exports: factory, - initialized: !0 - }, void 0; - deps.length || (deps = ["require", "exports", "module"]); - var req = function(childId) { - return window.acequire(id, childId) - }; - window.acequire.modules[id] = { - exports: {}, - factory: function() { - var module = this, - returnExports = factory.apply(this, deps.map(function(dep) { - switch (dep) { - case "require": - return req; - case "exports": - return module.exports; - case "module": - return module; - default: - return req(dep) - } - })); - return returnExports && (module.exports = returnExports), module - } - } - }, window.define.amd = {}, acequire.tlns = {}, window.initBaseUrls = function(topLevelNamespaces) { - for (var i in topLevelNamespaces) acequire.tlns[i] = topLevelNamespaces[i] - }, window.initSender = function() { - var EventEmitter = window.acequire("ace/lib/event_emitter").EventEmitter, - oop = window.acequire("ace/lib/oop"), - Sender = function() {}; - return function() { - oop.implement(this, EventEmitter), this.callback = function(data, callbackId) { - postMessage({ - type: "call", - id: callbackId, - data: data - }) - }, this.emit = function(name, data) { - postMessage({ - type: "event", - name: name, - data: data - }) - } - }.call(Sender.prototype), new Sender - }; - var main = window.main = null, - sender = window.sender = null; - window.onmessage = function(e) { - var msg = e.data; - if (msg.event && sender) sender._signal(msg.event, msg.data); - else if (msg.command) - if (main[msg.command]) main[msg.command].apply(main, msg.args); - else { - if (!window[msg.command]) throw Error("Unknown command:" + msg.command); - window[msg.command].apply(window, msg.args) - } - else if (msg.init) { - window.initBaseUrls(msg.tlns), acequire("ace/lib/es5-shim"), sender = window.sender = window.initSender(); - var clazz = acequire(msg.module)[msg.classname]; - main = window.main = new clazz(sender) - } - } - } -}(this), ace.define("ace/lib/oop", ["require", "exports", "module"], function(acequire, exports) { - "use strict"; - exports.inherits = function(ctor, superCtor) { - ctor.super_ = superCtor, ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: !1, - writable: !0, - configurable: !0 - } - }) - }, exports.mixin = function(obj, mixin) { - for (var key in mixin) obj[key] = mixin[key]; - return obj - }, exports.implement = function(proto, mixin) { - exports.mixin(proto, mixin) - } -}), ace.define("ace/range", ["require", "exports", "module"], function(acequire, exports) { - "use strict"; - var comparePoints = function(p1, p2) { - return p1.row - p2.row || p1.column - p2.column - }, - Range = function(startRow, startColumn, endRow, endColumn) { - this.start = { - row: startRow, - column: startColumn - }, this.end = { - row: endRow, - column: endColumn - } - }; - (function() { - this.isEqual = function(range) { - return this.start.row === range.start.row && this.end.row === range.end.row && this.start.column === range.start.column && this.end.column === range.end.column - }, this.toString = function() { - return "Range: [" + this.start.row + "/" + this.start.column + "] -> [" + this.end.row + "/" + this.end.column + "]" - }, this.contains = function(row, column) { - return 0 == this.compare(row, column) - }, this.compareRange = function(range) { - var cmp, end = range.end, - start = range.start; - return cmp = this.compare(end.row, end.column), 1 == cmp ? (cmp = this.compare(start.row, start.column), 1 == cmp ? 2 : 0 == cmp ? 1 : 0) : -1 == cmp ? -2 : (cmp = this.compare(start.row, start.column), -1 == cmp ? -1 : 1 == cmp ? 42 : 0) - }, this.comparePoint = function(p) { - return this.compare(p.row, p.column) - }, this.containsRange = function(range) { - return 0 == this.comparePoint(range.start) && 0 == this.comparePoint(range.end) - }, this.intersects = function(range) { - var cmp = this.compareRange(range); - return -1 == cmp || 0 == cmp || 1 == cmp - }, this.isEnd = function(row, column) { - return this.end.row == row && this.end.column == column - }, this.isStart = function(row, column) { - return this.start.row == row && this.start.column == column - }, this.setStart = function(row, column) { - "object" == typeof row ? (this.start.column = row.column, this.start.row = row.row) : (this.start.row = row, this.start.column = column) - }, this.setEnd = function(row, column) { - "object" == typeof row ? (this.end.column = row.column, this.end.row = row.row) : (this.end.row = row, this.end.column = column) - }, this.inside = function(row, column) { - return 0 == this.compare(row, column) ? this.isEnd(row, column) || this.isStart(row, column) ? !1 : !0 : !1 - }, this.insideStart = function(row, column) { - return 0 == this.compare(row, column) ? this.isEnd(row, column) ? !1 : !0 : !1 - }, this.insideEnd = function(row, column) { - return 0 == this.compare(row, column) ? this.isStart(row, column) ? !1 : !0 : !1 - }, this.compare = function(row, column) { - return this.isMultiLine() || row !== this.start.row ? this.start.row > row ? -1 : row > this.end.row ? 1 : this.start.row === row ? column >= this.start.column ? 0 : -1 : this.end.row === row ? this.end.column >= column ? 0 : 1 : 0 : this.start.column > column ? -1 : column > this.end.column ? 1 : 0 - }, this.compareStart = function(row, column) { - return this.start.row == row && this.start.column == column ? -1 : this.compare(row, column) - }, this.compareEnd = function(row, column) { - return this.end.row == row && this.end.column == column ? 1 : this.compare(row, column) - }, this.compareInside = function(row, column) { - return this.end.row == row && this.end.column == column ? 1 : this.start.row == row && this.start.column == column ? -1 : this.compare(row, column) - }, this.clipRows = function(firstRow, lastRow) { - if (this.end.row > lastRow) var end = { - row: lastRow + 1, - column: 0 - }; - else if (firstRow > this.end.row) var end = { - row: firstRow, - column: 0 - }; - if (this.start.row > lastRow) var start = { - row: lastRow + 1, - column: 0 - }; - else if (firstRow > this.start.row) var start = { - row: firstRow, - column: 0 - }; - return Range.fromPoints(start || this.start, end || this.end) - }, this.extend = function(row, column) { - var cmp = this.compare(row, column); - if (0 == cmp) return this; - if (-1 == cmp) var start = { - row: row, - column: column - }; - else var end = { - row: row, - column: column - }; - return Range.fromPoints(start || this.start, end || this.end) - }, this.isEmpty = function() { - return this.start.row === this.end.row && this.start.column === this.end.column - }, this.isMultiLine = function() { - return this.start.row !== this.end.row - }, this.clone = function() { - return Range.fromPoints(this.start, this.end) - }, this.collapseRows = function() { - return 0 == this.end.column ? new Range(this.start.row, 0, Math.max(this.start.row, this.end.row - 1), 0) : new Range(this.start.row, 0, this.end.row, 0) - }, this.toScreenRange = function(session) { - var screenPosStart = session.documentToScreenPosition(this.start), - screenPosEnd = session.documentToScreenPosition(this.end); - return new Range(screenPosStart.row, screenPosStart.column, screenPosEnd.row, screenPosEnd.column) - }, this.moveBy = function(row, column) { - this.start.row += row, this.start.column += column, this.end.row += row, this.end.column += column - } - }).call(Range.prototype), Range.fromPoints = function(start, end) { - return new Range(start.row, start.column, end.row, end.column) - }, Range.comparePoints = comparePoints, Range.comparePoints = function(p1, p2) { - return p1.row - p2.row || p1.column - p2.column - }, exports.Range = Range -}), ace.define("ace/apply_delta", ["require", "exports", "module"], function(acequire, exports) { - "use strict"; - exports.applyDelta = function(docLines, delta) { - var row = delta.start.row, - startColumn = delta.start.column, - line = docLines[row] || ""; - switch (delta.action) { - case "insert": - var lines = delta.lines; - if (1 === lines.length) docLines[row] = line.substring(0, startColumn) + delta.lines[0] + line.substring(startColumn); - else { - var args = [row, 1].concat(delta.lines); - docLines.splice.apply(docLines, args), docLines[row] = line.substring(0, startColumn) + docLines[row], docLines[row + delta.lines.length - 1] += line.substring(startColumn) - } - break; - case "remove": - var endColumn = delta.end.column, - endRow = delta.end.row; - row === endRow ? docLines[row] = line.substring(0, startColumn) + line.substring(endColumn) : docLines.splice(row, endRow - row + 1, line.substring(0, startColumn) + docLines[endRow].substring(endColumn)) - } - } -}), ace.define("ace/lib/event_emitter", ["require", "exports", "module"], function(acequire, exports) { - "use strict"; - var EventEmitter = {}, - stopPropagation = function() { - this.propagationStopped = !0 - }, - preventDefault = function() { - this.defaultPrevented = !0 - }; - EventEmitter._emit = EventEmitter._dispatchEvent = function(eventName, e) { - this._eventRegistry || (this._eventRegistry = {}), this._defaultHandlers || (this._defaultHandlers = {}); - var listeners = this._eventRegistry[eventName] || [], - defaultHandler = this._defaultHandlers[eventName]; - if (listeners.length || defaultHandler) { - "object" == typeof e && e || (e = {}), e.type || (e.type = eventName), e.stopPropagation || (e.stopPropagation = stopPropagation), e.preventDefault || (e.preventDefault = preventDefault), listeners = listeners.slice(); - for (var i = 0; listeners.length > i && (listeners[i](e, this), !e.propagationStopped); i++); - return defaultHandler && !e.defaultPrevented ? defaultHandler(e, this) : void 0 - } - }, EventEmitter._signal = function(eventName, e) { - var listeners = (this._eventRegistry || {})[eventName]; - if (listeners) { - listeners = listeners.slice(); - for (var i = 0; listeners.length > i; i++) listeners[i](e, this) - } - }, EventEmitter.once = function(eventName, callback) { - var _self = this; - callback && this.addEventListener(eventName, function newCallback() { - _self.removeEventListener(eventName, newCallback), callback.apply(null, arguments) - }) - }, EventEmitter.setDefaultHandler = function(eventName, callback) { - var handlers = this._defaultHandlers; - if (handlers || (handlers = this._defaultHandlers = { - _disabled_: {} - }), handlers[eventName]) { - var old = handlers[eventName], - disabled = handlers._disabled_[eventName]; - disabled || (handlers._disabled_[eventName] = disabled = []), disabled.push(old); - var i = disabled.indexOf(callback); - 1 != i && disabled.splice(i, 1) - } - handlers[eventName] = callback - }, EventEmitter.removeDefaultHandler = function(eventName, callback) { - var handlers = this._defaultHandlers; - if (handlers) { - var disabled = handlers._disabled_[eventName]; - if (handlers[eventName] == callback) handlers[eventName], disabled && this.setDefaultHandler(eventName, disabled.pop()); - else if (disabled) { - var i = disabled.indexOf(callback); - 1 != i && disabled.splice(i, 1) - } - } - }, EventEmitter.on = EventEmitter.addEventListener = function(eventName, callback, capturing) { - this._eventRegistry = this._eventRegistry || {}; - var listeners = this._eventRegistry[eventName]; - return listeners || (listeners = this._eventRegistry[eventName] = []), -1 == listeners.indexOf(callback) && listeners[capturing ? "unshift" : "push"](callback), callback - }, EventEmitter.off = EventEmitter.removeListener = EventEmitter.removeEventListener = function(eventName, callback) { - this._eventRegistry = this._eventRegistry || {}; - var listeners = this._eventRegistry[eventName]; - if (listeners) { - var index = listeners.indexOf(callback); - 1 !== index && listeners.splice(index, 1) - } - }, EventEmitter.removeAllListeners = function(eventName) { - this._eventRegistry && (this._eventRegistry[eventName] = []) - }, exports.EventEmitter = EventEmitter -}), ace.define("ace/anchor", ["require", "exports", "module", "ace/lib/oop", "ace/lib/event_emitter"], function(acequire, exports) { - "use strict"; - var oop = acequire("./lib/oop"), - EventEmitter = acequire("./lib/event_emitter").EventEmitter, - Anchor = exports.Anchor = function(doc, row, column) { - this.$onChange = this.onChange.bind(this), this.attach(doc), column === void 0 ? this.setPosition(row.row, row.column) : this.setPosition(row, column) - }; - (function() { - function $pointsInOrder(point1, point2, equalPointsInOrder) { - var bColIsAfter = equalPointsInOrder ? point1.column <= point2.column : point1.column < point2.column; - return point1.row < point2.row || point1.row == point2.row && bColIsAfter - } - - function $getTransformedPoint(delta, point, moveIfEqual) { - var deltaIsInsert = "insert" == delta.action, - deltaRowShift = (deltaIsInsert ? 1 : -1) * (delta.end.row - delta.start.row), - deltaColShift = (deltaIsInsert ? 1 : -1) * (delta.end.column - delta.start.column), - deltaStart = delta.start, - deltaEnd = deltaIsInsert ? deltaStart : delta.end; - return $pointsInOrder(point, deltaStart, moveIfEqual) ? { - row: point.row, - column: point.column - } : $pointsInOrder(deltaEnd, point, !moveIfEqual) ? { - row: point.row + deltaRowShift, - column: point.column + (point.row == deltaEnd.row ? deltaColShift : 0) - } : { - row: deltaStart.row, - column: deltaStart.column - } - } - oop.implement(this, EventEmitter), this.getPosition = function() { - return this.$clipPositionToDocument(this.row, this.column) - }, this.getDocument = function() { - return this.document - }, this.$insertRight = !1, this.onChange = function(delta) { - if (!(delta.start.row == delta.end.row && delta.start.row != this.row || delta.start.row > this.row)) { - var point = $getTransformedPoint(delta, { - row: this.row, - column: this.column - }, this.$insertRight); - this.setPosition(point.row, point.column, !0) - } - }, this.setPosition = function(row, column, noClip) { - var pos; - if (pos = noClip ? { - row: row, - column: column - } : this.$clipPositionToDocument(row, column), this.row != pos.row || this.column != pos.column) { - var old = { - row: this.row, - column: this.column - }; - this.row = pos.row, this.column = pos.column, this._signal("change", { - old: old, - value: pos - }) - } - }, this.detach = function() { - this.document.removeEventListener("change", this.$onChange) - }, this.attach = function(doc) { - this.document = doc || this.document, this.document.on("change", this.$onChange) - }, this.$clipPositionToDocument = function(row, column) { - var pos = {}; - return row >= this.document.getLength() ? (pos.row = Math.max(0, this.document.getLength() - 1), pos.column = this.document.getLine(pos.row).length) : 0 > row ? (pos.row = 0, pos.column = 0) : (pos.row = row, pos.column = Math.min(this.document.getLine(pos.row).length, Math.max(0, column))), 0 > column && (pos.column = 0), pos - } - }).call(Anchor.prototype) -}), ace.define("ace/document", ["require", "exports", "module", "ace/lib/oop", "ace/apply_delta", "ace/lib/event_emitter", "ace/range", "ace/anchor"], function(acequire, exports) { - "use strict"; - var oop = acequire("./lib/oop"), - applyDelta = acequire("./apply_delta").applyDelta, - EventEmitter = acequire("./lib/event_emitter").EventEmitter, - Range = acequire("./range").Range, - Anchor = acequire("./anchor").Anchor, - Document = function(textOrLines) { - this.$lines = [""], 0 === textOrLines.length ? this.$lines = [""] : Array.isArray(textOrLines) ? this.insertMergedLines({ - row: 0, - column: 0 - }, textOrLines) : this.insert({ - row: 0, - column: 0 - }, textOrLines) - }; - (function() { - oop.implement(this, EventEmitter), this.setValue = function(text) { - var len = this.getLength() - 1; - this.remove(new Range(0, 0, len, this.getLine(len).length)), this.insert({ - row: 0, - column: 0 - }, text) - }, this.getValue = function() { - return this.getAllLines().join(this.getNewLineCharacter()) - }, this.createAnchor = function(row, column) { - return new Anchor(this, row, column) - }, this.$split = 0 === "aaa".split(/a/).length ? function(text) { - return text.replace(/\r\n|\r/g, "\n").split("\n"); - } : function(text) { - return text.split(/\r\n|\r|\n/); - }, this.$detectNewLine = function(text) { - var match = text.match(/^.*?(\r\n|\r|\n)/m); - this.$autoNewLine = match ? match[1] : "\n", this._signal("changeNewLineMode") - }, this.getNewLineCharacter = function() { - switch (this.$newLineMode) { - case "windows": - return "\r\n"; - case "unix": - return "\n"; - default: - return this.$autoNewLine || "\n" - } - }, this.$autoNewLine = "", this.$newLineMode = "auto", this.setNewLineMode = function(newLineMode) { - this.$newLineMode !== newLineMode && (this.$newLineMode = newLineMode, this._signal("changeNewLineMode")) - }, this.getNewLineMode = function() { - return this.$newLineMode - }, this.isNewLine = function(text) { - return "\r\n" == text || "\r" == text || "\n" == text - }, this.getLine = function(row) { - return this.$lines[row] || "" - }, this.getLines = function(firstRow, lastRow) { - return this.$lines.slice(firstRow, lastRow + 1) - }, this.getAllLines = function() { - return this.getLines(0, this.getLength()) - }, this.getLength = function() { - return this.$lines.length - }, this.getTextRange = function(range) { - return this.getLinesForRange(range).join(this.getNewLineCharacter()) - }, this.getLinesForRange = function(range) { - var lines; - if (range.start.row === range.end.row) lines = [this.getLine(range.start.row).substring(range.start.column, range.end.column)]; - else { - lines = this.getLines(range.start.row, range.end.row), lines[0] = (lines[0] || "").substring(range.start.column); - var l = lines.length - 1; - range.end.row - range.start.row == l && (lines[l] = lines[l].substring(0, range.end.column)) - } - return lines - }, this.insertLines = function(row, lines) { - return console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead."), this.insertFullLines(row, lines) - }, this.removeLines = function(firstRow, lastRow) { - return console.warn("Use of document.removeLines is deprecated. Use the removeFullLines method instead."), this.removeFullLines(firstRow, lastRow) - }, this.insertNewLine = function(position) { - return console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, ['', '']) instead."), this.insertMergedLines(position, ["", ""]) - }, this.insert = function(position, text) { - return 1 >= this.getLength() && this.$detectNewLine(text), this.insertMergedLines(position, this.$split(text)) - }, this.insertInLine = function(position, text) { - var start = this.clippedPos(position.row, position.column), - end = this.pos(position.row, position.column + text.length); - return this.applyDelta({ - start: start, - end: end, - action: "insert", - lines: [text] - }, !0), this.clonePos(end) - }, this.clippedPos = function(row, column) { - var length = this.getLength(); - void 0 === row ? row = length : 0 > row ? row = 0 : row >= length && (row = length - 1, column = void 0); - var line = this.getLine(row); - return void 0 == column && (column = line.length), column = Math.min(Math.max(column, 0), line.length), { - row: row, - column: column - } - }, this.clonePos = function(pos) { - return { - row: pos.row, - column: pos.column - } - }, this.pos = function(row, column) { - return { - row: row, - column: column - } - }, this.$clipPosition = function(position) { - var length = this.getLength(); - return position.row >= length ? (position.row = Math.max(0, length - 1), position.column = this.getLine(length - 1).length) : (position.row = Math.max(0, position.row), position.column = Math.min(Math.max(position.column, 0), this.getLine(position.row).length)), position - }, this.insertFullLines = function(row, lines) { - row = Math.min(Math.max(row, 0), this.getLength()); - var column = 0; - this.getLength() > row ? (lines = lines.concat([""]), column = 0) : (lines = [""].concat(lines), row--, column = this.$lines[row].length), this.insertMergedLines({ - row: row, - column: column - }, lines) - }, this.insertMergedLines = function(position, lines) { - var start = this.clippedPos(position.row, position.column), - end = { - row: start.row + lines.length - 1, - column: (1 == lines.length ? start.column : 0) + lines[lines.length - 1].length - }; - return this.applyDelta({ - start: start, - end: end, - action: "insert", - lines: lines - }), this.clonePos(end) - }, this.remove = function(range) { - var start = this.clippedPos(range.start.row, range.start.column), - end = this.clippedPos(range.end.row, range.end.column); - return this.applyDelta({ - start: start, - end: end, - action: "remove", - lines: this.getLinesForRange({ - start: start, - end: end - }) - }), this.clonePos(start) - }, this.removeInLine = function(row, startColumn, endColumn) { - var start = this.clippedPos(row, startColumn), - end = this.clippedPos(row, endColumn); - return this.applyDelta({ - start: start, - end: end, - action: "remove", - lines: this.getLinesForRange({ - start: start, - end: end - }) - }, !0), this.clonePos(start) - }, this.removeFullLines = function(firstRow, lastRow) { - firstRow = Math.min(Math.max(0, firstRow), this.getLength() - 1), lastRow = Math.min(Math.max(0, lastRow), this.getLength() - 1); - var deleteFirstNewLine = lastRow == this.getLength() - 1 && firstRow > 0, - deleteLastNewLine = this.getLength() - 1 > lastRow, - startRow = deleteFirstNewLine ? firstRow - 1 : firstRow, - startCol = deleteFirstNewLine ? this.getLine(startRow).length : 0, - endRow = deleteLastNewLine ? lastRow + 1 : lastRow, - endCol = deleteLastNewLine ? 0 : this.getLine(endRow).length, - range = new Range(startRow, startCol, endRow, endCol), - deletedLines = this.$lines.slice(firstRow, lastRow + 1); - return this.applyDelta({ - start: range.start, - end: range.end, - action: "remove", - lines: this.getLinesForRange(range) - }), deletedLines - }, this.removeNewLine = function(row) { - this.getLength() - 1 > row && row >= 0 && this.applyDelta({ - start: this.pos(row, this.getLine(row).length), - end: this.pos(row + 1, 0), - action: "remove", - lines: ["", ""] - }) - }, this.replace = function(range, text) { - if (range instanceof Range || (range = Range.fromPoints(range.start, range.end)), 0 === text.length && range.isEmpty()) return range.start; - if (text == this.getTextRange(range)) return range.end; - this.remove(range); - var end; - return end = text ? this.insert(range.start, text) : range.start - }, this.applyDeltas = function(deltas) { - for (var i = 0; deltas.length > i; i++) this.applyDelta(deltas[i]) - }, this.revertDeltas = function(deltas) { - for (var i = deltas.length - 1; i >= 0; i--) this.revertDelta(deltas[i]) - }, this.applyDelta = function(delta, doNotValidate) { - var isInsert = "insert" == delta.action; - (isInsert ? 1 >= delta.lines.length && !delta.lines[0] : !Range.comparePoints(delta.start, delta.end)) || (isInsert && delta.lines.length > 2e4 && this.$splitAndapplyLargeDelta(delta, 2e4), applyDelta(this.$lines, delta, doNotValidate), this._signal("change", delta)) - }, this.$splitAndapplyLargeDelta = function(delta, MAX) { - for (var lines = delta.lines, l = lines.length, row = delta.start.row, column = delta.start.column, from = 0, to = 0;;) { - from = to, to += MAX - 1; - var chunk = lines.slice(from, to); - if (to > l) { - delta.lines = chunk, delta.start.row = row + from, delta.start.column = column; - break - } - chunk.push(""), this.applyDelta({ - start: this.pos(row + from, column), - end: this.pos(row + to, column = 0), - action: delta.action, - lines: chunk - }, !0) - } - }, this.revertDelta = function(delta) { - this.applyDelta({ - start: this.clonePos(delta.start), - end: this.clonePos(delta.end), - action: "insert" == delta.action ? "remove" : "insert", - lines: delta.lines.slice() - }) - }, this.indexToPosition = function(index, startRow) { - for (var lines = this.$lines || this.getAllLines(), newlineLength = this.getNewLineCharacter().length, i = startRow || 0, l = lines.length; l > i; i++) - if (index -= lines[i].length + newlineLength, 0 > index) return { - row: i, - column: index + lines[i].length + newlineLength - }; - return { - row: l - 1, - column: lines[l - 1].length - } - }, this.positionToIndex = function(pos, startRow) { - for (var lines = this.$lines || this.getAllLines(), newlineLength = this.getNewLineCharacter().length, index = 0, row = Math.min(pos.row, lines.length), i = startRow || 0; row > i; ++i) index += lines[i].length + newlineLength; - return index + pos.column - } - }).call(Document.prototype), exports.Document = Document -}), ace.define("ace/lib/lang", ["require", "exports", "module"], function(acequire, exports) { - "use strict"; - exports.last = function(a) { - return a[a.length - 1] - }, exports.stringReverse = function(string) { - return string.split("").reverse().join("") - }, exports.stringRepeat = function(string, count) { - for (var result = ""; count > 0;) 1 & count && (result += string), (count >>= 1) && (string += string); - return result - }; - var trimBeginRegexp = /^\s\s*/, - trimEndRegexp = /\s\s*$/; - exports.stringTrimLeft = function(string) { - return string.replace(trimBeginRegexp, "") - }, exports.stringTrimRight = function(string) { - return string.replace(trimEndRegexp, "") - }, exports.copyObject = function(obj) { - var copy = {}; - for (var key in obj) copy[key] = obj[key]; - return copy - }, exports.copyArray = function(array) { - for (var copy = [], i = 0, l = array.length; l > i; i++) copy[i] = array[i] && "object" == typeof array[i] ? this.copyObject(array[i]) : array[i]; - return copy - }, exports.deepCopy = function deepCopy(obj) { - if ("object" != typeof obj || !obj) return obj; - var copy; - if (Array.isArray(obj)) { - copy = []; - for (var key = 0; obj.length > key; key++) copy[key] = deepCopy(obj[key]); - return copy - } - if ("[object Object]" !== Object.prototype.toString.call(obj)) return obj; - copy = {}; - for (var key in obj) copy[key] = deepCopy(obj[key]); - return copy - }, exports.arrayToMap = function(arr) { - for (var map = {}, i = 0; arr.length > i; i++) map[arr[i]] = 1; - return map - }, exports.createMap = function(props) { - var map = Object.create(null); - for (var i in props) map[i] = props[i]; - return map - }, exports.arrayRemove = function(array, value) { - for (var i = 0; array.length >= i; i++) value === array[i] && array.splice(i, 1) - }, exports.escapeRegExp = function(str) { - return str.replace(/([.*+?^${}()|[\]\/\\])/g, "\\$1"); - }, exports.escapeHTML = function(str) { - return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/ i; i += 2) { - if (Array.isArray(data[i + 1])) var d = { - action: "insert", - start: data[i], - lines: data[i + 1] - }; - else var d = { - action: "remove", - start: data[i], - end: data[i + 1] - }; - doc.applyDelta(d, !0) - } - return _self.$timeout ? deferredUpdate.schedule(_self.$timeout) : (_self.onUpdate(), void 0) - }) - }; - (function() { - this.$timeout = 500, this.setTimeout = function(timeout) { - this.$timeout = timeout - }, this.setValue = function(value) { - this.doc.setValue(value), this.deferredUpdate.schedule(this.$timeout) - }, this.getValue = function(callbackId) { - this.sender.callback(this.doc.getValue(), callbackId) - }, this.onUpdate = function() {}, this.isPending = function() { - return this.deferredUpdate.isPending() - } - }).call(Mirror.prototype) -}), ace.define("ace/mode/json/json_parse", ["require", "exports", "module"], function() { - "use strict"; - var at, ch, text, value, escapee = { - '"': '"', - "\\": "\\", - "/": "/", - b: "\b", - f: "\f", - n: "\n", - r: "\r", - t: " " - }, - error = function(m) { - throw { - name: "SyntaxError", - message: m, - at: at, - text: text - } - }, - reset = function (newAt) { - ch = text.charAt(newAt); - at = newAt + 1; - }, - next = function(c) { - return c && c !== ch && error("Expected '" + c + "' instead of '" + ch + "'"), ch = text.charAt(at), at += 1, ch - }, - nextUpTo = function (upTo, errorMessage) { - let currentAt = at, - i = text.indexOf(upTo, currentAt); - if (i < 0) { - error(errorMessage || 'Expected \'' + upTo + '\''); - } - reset(i + upTo.length); - return text.substring(currentAt, i); - }, - peek = function (c) { - return text.substr(at, c.length) === c; // nocommit - double check - }, - number = function() { - var number, string = ""; - for ("-" === ch && (string = "-", next("-")); ch >= "0" && "9" >= ch;) string += ch, next(); - if ("." === ch) - for (string += "."; next() && ch >= "0" && "9" >= ch;) string += ch; - if ("e" === ch || "E" === ch) - for (string += ch, next(), ("-" === ch || "+" === ch) && (string += ch, next()); ch >= "0" && "9" >= ch;) string += ch, next(); - return number = +string, isNaN(number) ? (error("Bad number"), void 0) : number - }, - string = function() { - var hex, i, uffff, string = ""; - if ('"' === ch) { - if (peek('""')) { - // literal - next('"'); - next('"'); - return nextUpTo('"""', 'failed to find closing \'"""\''); - } else { - for (; next();) { - if ('"' === ch) return next(), string; - if ("\\" === ch) - if (next(), "u" === ch) { - for (uffff = 0, i = 0; 4 > i && (hex = parseInt(next(), 16), isFinite(hex)); i += 1) uffff = 16 * uffff + hex; - string += String.fromCharCode(uffff) - } else { - if ("string" != typeof escapee[ch]) break; - string += escapee[ch] - } - else string += ch - } - } - } - error("Bad string") - }, - white = function() { - for (; ch && " " >= ch;) next() - }, - word = function() { - switch (ch) { - case "t": - return next("t"), next("r"), next("u"), next("e"), !0; - case "f": - return next("f"), next("a"), next("l"), next("s"), next("e"), !1; - case "n": - return next("n"), next("u"), next("l"), next("l"), null - } - error("Unexpected '" + ch + "'") - }, - array = function() { - var array = []; - if ("[" === ch) { - if (next("["), white(), "]" === ch) return next("]"), array; - for (; ch;) { - if (array.push(value()), white(), "]" === ch) return next("]"), array; - next(","), white() - } - } - error("Bad array") - }, - object = function() { - var key, object = {}; - if ("{" === ch) { - if (next("{"), white(), "}" === ch) return next("}"), object; - for (; ch;) { - if (key = string(), white(), next(":"), Object.hasOwnProperty.call(object, key) && error('Duplicate key "' + key + '"'), object[key] = value(), white(), "}" === ch) return next("}"), object; - next(","), white() - } - } - error("Bad object") - }; - return value = function() { - switch (white(), ch) { - case "{": - return object(); - case "[": - return array(); - case '"': - return string(); - case "-": - return number(); - default: - return ch >= "0" && "9" >= ch ? number() : word() - } - }, - function(source, reviver) { - var result; - return text = source, at = 0, ch = " ", result = value(), white(), ch && error("Syntax error"), "function" == typeof reviver ? function walk(holder, key) { - var k, v, value = holder[key]; - if (value && "object" == typeof value) - for (k in value) Object.hasOwnProperty.call(value, k) && (v = walk(value, k), void 0 !== v ? value[k] = v : delete value[k]); - return reviver.call(holder, key, value) - }({ - "": result - }, "") : result - } -}), ace.define("ace/mode/json_worker", ["require", "exports", "module", "ace/lib/oop", "ace/worker/mirror", "ace/mode/json/json_parse"], function(acequire, exports) { - "use strict"; - var oop = acequire("../lib/oop"), - Mirror = acequire("../worker/mirror").Mirror, - parse = acequire("./json/json_parse"), - JsonWorker = exports.JsonWorker = function(sender) { - Mirror.call(this, sender), this.setTimeout(200) - }; - oop.inherits(JsonWorker, Mirror), - function() { - this.onUpdate = function() { - var value = this.doc.getValue(), - errors = []; - try { - value && parse(value) - } catch (e) { - var pos = this.doc.indexToPosition(e.at - 1); - errors.push({ - row: pos.row, - column: pos.column, - text: e.message, - type: "error" - }) - } - this.sender.emit("annotate", errors) - } - }.call(JsonWorker.prototype) -}), ace.define("ace/lib/es5-shim", ["require", "exports", "module"], function() { - function Empty() {} - - function doesDefinePropertyWork(object) { - try { - return Object.defineProperty(object, "sentinel", {}), "sentinel" in object - } catch (exception) {} - } - - function toInteger(n) { - return n = +n, n !== n ? n = 0 : 0 !== n && n !== 1 / 0 && n !== -(1 / 0) && (n = (n > 0 || -1) * Math.floor(Math.abs(n))), n - } - Function.prototype.bind || (Function.prototype.bind = function(that) { - var target = this; - if ("function" != typeof target) throw new TypeError("Function.prototype.bind called on incompatible " + target); - var args = slice.call(arguments, 1), - bound = function() { - if (this instanceof bound) { - var result = target.apply(this, args.concat(slice.call(arguments))); - return Object(result) === result ? result : this - } - return target.apply(that, args.concat(slice.call(arguments))) - }; - return target.prototype && (Empty.prototype = target.prototype, bound.prototype = new Empty, Empty.prototype = null), bound - }); - var defineGetter, defineSetter, lookupGetter, lookupSetter, supportsAccessors, call = Function.prototype.call, - prototypeOfArray = Array.prototype, - prototypeOfObject = Object.prototype, - slice = prototypeOfArray.slice, - _toString = call.bind(prototypeOfObject.toString), - owns = call.bind(prototypeOfObject.hasOwnProperty); - if ((supportsAccessors = owns(prototypeOfObject, "__defineGetter__")) && (defineGetter = call.bind(prototypeOfObject.__defineGetter__), defineSetter = call.bind(prototypeOfObject.__defineSetter__), lookupGetter = call.bind(prototypeOfObject.__lookupGetter__), lookupSetter = call.bind(prototypeOfObject.__lookupSetter__)), 2 != [1, 2].splice(0).length) - if (function() { - function makeArray(l) { - var a = Array(l + 2); - return a[0] = a[1] = 0, a - } - var lengthBefore, array = []; - return array.splice.apply(array, makeArray(20)), array.splice.apply(array, makeArray(26)), lengthBefore = array.length, array.splice(5, 0, "XXX"), lengthBefore + 1 == array.length, lengthBefore + 1 == array.length ? !0 : void 0 - }()) { - var array_splice = Array.prototype.splice; - Array.prototype.splice = function(start, deleteCount) { - return arguments.length ? array_splice.apply(this, [void 0 === start ? 0 : start, void 0 === deleteCount ? this.length - start : deleteCount].concat(slice.call(arguments, 2))) : [] - } - } else Array.prototype.splice = function(pos, removeCount) { - var length = this.length; - pos > 0 ? pos > length && (pos = length) : void 0 == pos ? pos = 0 : 0 > pos && (pos = Math.max(length + pos, 0)), length > pos + removeCount || (removeCount = length - pos); - var removed = this.slice(pos, pos + removeCount), - insert = slice.call(arguments, 2), - add = insert.length; - if (pos === length) add && this.push.apply(this, insert); - else { - var remove = Math.min(removeCount, length - pos), - tailOldPos = pos + remove, - tailNewPos = tailOldPos + add - remove, - tailCount = length - tailOldPos, - lengthAfterRemove = length - remove; - if (tailOldPos > tailNewPos) - for (var i = 0; tailCount > i; ++i) this[tailNewPos + i] = this[tailOldPos + i]; - else if (tailNewPos > tailOldPos) - for (i = tailCount; i--;) this[tailNewPos + i] = this[tailOldPos + i]; - if (add && pos === lengthAfterRemove) this.length = lengthAfterRemove, this.push.apply(this, insert); - else - for (this.length = lengthAfterRemove + add, i = 0; add > i; ++i) this[pos + i] = insert[i] - } - return removed - }; - Array.isArray || (Array.isArray = function(obj) { - return "[object Array]" == _toString(obj) - }); - var boxedString = Object("a"), - splitString = "a" != boxedString[0] || !(0 in boxedString); - if (Array.prototype.forEach || (Array.prototype.forEach = function(fun) { - var object = toObject(this), - self = splitString && "[object String]" == _toString(this) ? this.split("") : object, - thisp = arguments[1], - i = -1, - length = self.length >>> 0; - if ("[object Function]" != _toString(fun)) throw new TypeError; - for (; length > ++i;) i in self && fun.call(thisp, self[i], i, object) - }), Array.prototype.map || (Array.prototype.map = function(fun) { - var object = toObject(this), - self = splitString && "[object String]" == _toString(this) ? this.split("") : object, - length = self.length >>> 0, - result = Array(length), - thisp = arguments[1]; - if ("[object Function]" != _toString(fun)) throw new TypeError(fun + " is not a function"); - for (var i = 0; length > i; i++) i in self && (result[i] = fun.call(thisp, self[i], i, object)); - return result - }), Array.prototype.filter || (Array.prototype.filter = function(fun) { - var value, object = toObject(this), - self = splitString && "[object String]" == _toString(this) ? this.split("") : object, - length = self.length >>> 0, - result = [], - thisp = arguments[1]; - if ("[object Function]" != _toString(fun)) throw new TypeError(fun + " is not a function"); - for (var i = 0; length > i; i++) i in self && (value = self[i], fun.call(thisp, value, i, object) && result.push(value)); - return result - }), Array.prototype.every || (Array.prototype.every = function(fun) { - var object = toObject(this), - self = splitString && "[object String]" == _toString(this) ? this.split("") : object, - length = self.length >>> 0, - thisp = arguments[1]; - if ("[object Function]" != _toString(fun)) throw new TypeError(fun + " is not a function"); - for (var i = 0; length > i; i++) - if (i in self && !fun.call(thisp, self[i], i, object)) return !1; - return !0 - }), Array.prototype.some || (Array.prototype.some = function(fun) { - var object = toObject(this), - self = splitString && "[object String]" == _toString(this) ? this.split("") : object, - length = self.length >>> 0, - thisp = arguments[1]; - if ("[object Function]" != _toString(fun)) throw new TypeError(fun + " is not a function"); - for (var i = 0; length > i; i++) - if (i in self && fun.call(thisp, self[i], i, object)) return !0; - return !1 - }), Array.prototype.reduce || (Array.prototype.reduce = function(fun) { - var object = toObject(this), - self = splitString && "[object String]" == _toString(this) ? this.split("") : object, - length = self.length >>> 0; - if ("[object Function]" != _toString(fun)) throw new TypeError(fun + " is not a function"); - if (!length && 1 == arguments.length) throw new TypeError("reduce of empty array with no initial value"); - var result, i = 0; - if (arguments.length >= 2) result = arguments[1]; - else - for (;;) { - if (i in self) { - result = self[i++]; - break - } - if (++i >= length) throw new TypeError("reduce of empty array with no initial value") - } - for (; length > i; i++) i in self && (result = fun.call(void 0, result, self[i], i, object)); - return result - }), Array.prototype.reduceRight || (Array.prototype.reduceRight = function(fun) { - var object = toObject(this), - self = splitString && "[object String]" == _toString(this) ? this.split("") : object, - length = self.length >>> 0; - if ("[object Function]" != _toString(fun)) throw new TypeError(fun + " is not a function"); - if (!length && 1 == arguments.length) throw new TypeError("reduceRight of empty array with no initial value"); - var result, i = length - 1; - if (arguments.length >= 2) result = arguments[1]; - else - for (;;) { - if (i in self) { - result = self[i--]; - break - } - if (0 > --i) throw new TypeError("reduceRight of empty array with no initial value") - } - do i in this && (result = fun.call(void 0, result, self[i], i, object)); while (i--); - return result - }), Array.prototype.indexOf && -1 == [0, 1].indexOf(1, 2) || (Array.prototype.indexOf = function(sought) { - var self = splitString && "[object String]" == _toString(this) ? this.split("") : toObject(this), - length = self.length >>> 0; - if (!length) return -1; - var i = 0; - for (arguments.length > 1 && (i = toInteger(arguments[1])), i = i >= 0 ? i : Math.max(0, length + i); length > i; i++) - if (i in self && self[i] === sought) return i; - return -1 - }), Array.prototype.lastIndexOf && -1 == [0, 1].lastIndexOf(0, -3) || (Array.prototype.lastIndexOf = function(sought) { - var self = splitString && "[object String]" == _toString(this) ? this.split("") : toObject(this), - length = self.length >>> 0; - if (!length) return -1; - var i = length - 1; - for (arguments.length > 1 && (i = Math.min(i, toInteger(arguments[1]))), i = i >= 0 ? i : length - Math.abs(i); i >= 0; i--) - if (i in self && sought === self[i]) return i; - return -1 - }), Object.getPrototypeOf || (Object.getPrototypeOf = function(object) { - return object.__proto__ || (object.constructor ? object.constructor.prototype : prototypeOfObject) - }), !Object.getOwnPropertyDescriptor) { - var ERR_NON_OBJECT = "Object.getOwnPropertyDescriptor called on a non-object: "; - Object.getOwnPropertyDescriptor = function(object, property) { - if ("object" != typeof object && "function" != typeof object || null === object) throw new TypeError(ERR_NON_OBJECT + object); - if (owns(object, property)) { - var descriptor, getter, setter; - if (descriptor = { - enumerable: !0, - configurable: !0 - }, supportsAccessors) { - var prototype = object.__proto__; - object.__proto__ = prototypeOfObject; - var getter = lookupGetter(object, property), - setter = lookupSetter(object, property); - if (object.__proto__ = prototype, getter || setter) return getter && (descriptor.get = getter), setter && (descriptor.set = setter), descriptor - } - return descriptor.value = object[property], descriptor - } - } - } - if (Object.getOwnPropertyNames || (Object.getOwnPropertyNames = function(object) { - return Object.keys(object) - }), !Object.create) { - var createEmpty; - createEmpty = null === Object.prototype.__proto__ ? function() { - return { - __proto__: null - } - } : function() { - var empty = {}; - for (var i in empty) empty[i] = null; - return empty.constructor = empty.hasOwnProperty = empty.propertyIsEnumerable = empty.isPrototypeOf = empty.toLocaleString = empty.toString = empty.valueOf = empty.__proto__ = null, empty - }, Object.create = function(prototype, properties) { - var object; - if (null === prototype) object = createEmpty(); - else { - if ("object" != typeof prototype) throw new TypeError("typeof prototype[" + typeof prototype + "] != 'object'"); - var Type = function() {}; - Type.prototype = prototype, object = new Type, object.__proto__ = prototype - } - return void 0 !== properties && Object.defineProperties(object, properties), object - } - } - if (Object.defineProperty) { - var definePropertyWorksOnObject = doesDefinePropertyWork({}), - definePropertyWorksOnDom = "undefined" == typeof document || doesDefinePropertyWork(document.createElement("div")); - if (!definePropertyWorksOnObject || !definePropertyWorksOnDom) var definePropertyFallback = Object.defineProperty - } - if (!Object.defineProperty || definePropertyFallback) { - var ERR_NON_OBJECT_DESCRIPTOR = "Property description must be an object: ", - ERR_NON_OBJECT_TARGET = "Object.defineProperty called on non-object: ", - ERR_ACCESSORS_NOT_SUPPORTED = "getters & setters can not be defined on this javascript engine"; - Object.defineProperty = function(object, property, descriptor) { - if ("object" != typeof object && "function" != typeof object || null === object) throw new TypeError(ERR_NON_OBJECT_TARGET + object); - if ("object" != typeof descriptor && "function" != typeof descriptor || null === descriptor) throw new TypeError(ERR_NON_OBJECT_DESCRIPTOR + descriptor); - if (definePropertyFallback) try { - return definePropertyFallback.call(Object, object, property, descriptor) - } catch (exception) {} - if (owns(descriptor, "value")) - if (supportsAccessors && (lookupGetter(object, property) || lookupSetter(object, property))) { - var prototype = object.__proto__; - object.__proto__ = prototypeOfObject, delete object[property], object[property] = descriptor.value, object.__proto__ = prototype - } else object[property] = descriptor.value; - else { - if (!supportsAccessors) throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED); - owns(descriptor, "get") && defineGetter(object, property, descriptor.get), owns(descriptor, "set") && defineSetter(object, property, descriptor.set) - } - return object - } - } - Object.defineProperties || (Object.defineProperties = function(object, properties) { - for (var property in properties) owns(properties, property) && Object.defineProperty(object, property, properties[property]); - return object - }), Object.seal || (Object.seal = function(object) { - return object - }), Object.freeze || (Object.freeze = function(object) { - return object - }); - try { - Object.freeze(function() {}) - } catch (exception) { - Object.freeze = function(freezeObject) { - return function(object) { - return "function" == typeof object ? object : freezeObject(object) - } - }(Object.freeze) - } - if (Object.preventExtensions || (Object.preventExtensions = function(object) { - return object - }), Object.isSealed || (Object.isSealed = function() { - return !1 - }), Object.isFrozen || (Object.isFrozen = function() { - return !1 - }), Object.isExtensible || (Object.isExtensible = function(object) { - if (Object(object) === object) throw new TypeError; - for (var name = ""; owns(object, name);) name += "?"; - object[name] = !0; - var returnValue = owns(object, name); - return delete object[name], returnValue - }), !Object.keys) { - var hasDontEnumBug = !0, - dontEnums = ["toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "constructor"], - dontEnumsLength = dontEnums.length; - for (var key in { - toString: null - }) hasDontEnumBug = !1; - Object.keys = function(object) { - if ("object" != typeof object && "function" != typeof object || null === object) throw new TypeError("Object.keys called on a non-object"); - var keys = []; - for (var name in object) owns(object, name) && keys.push(name); - if (hasDontEnumBug) - for (var i = 0, ii = dontEnumsLength; ii > i; i++) { - var dontEnum = dontEnums[i]; - owns(object, dontEnum) && keys.push(dontEnum) - } - return keys - } - } - Date.now || (Date.now = function() { - return (new Date).getTime() - }); - var ws = " \n \f\r   ᠎              \u2028\u2029"; - if (!String.prototype.trim || ws.trim()) { - ws = "[" + ws + "]"; - var trimBeginRegexp = RegExp("^" + ws + ws + "*"), - trimEndRegexp = RegExp(ws + ws + "*$"); - String.prototype.trim = function() { - return (this + "").replace(trimBeginRegexp, "").replace(trimEndRegexp, "") - } - } - var toObject = function(o) { - if (null == o) throw new TypeError("can't convert " + o + " to object"); - return Object(o) - } -}); diff --git a/packages/kbn-ace/src/ace/modes/x_json/x_json.ts b/packages/kbn-ace/src/ace/modes/x_json/x_json.ts deleted file mode 100644 index 5a535e237a327..0000000000000 --- a/packages/kbn-ace/src/ace/modes/x_json/x_json.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import ace from 'brace'; -import { XJsonHighlightRules } from '..'; -import { workerModule } from './worker'; - -const { WorkerClient } = ace.acequire('ace/worker/worker_client'); - -const oop = ace.acequire('ace/lib/oop'); - -const { Mode: JSONMode } = ace.acequire('ace/mode/json'); -const { Tokenizer: AceTokenizer } = ace.acequire('ace/tokenizer'); -const { MatchingBraceOutdent } = ace.acequire('ace/mode/matching_brace_outdent'); -const { CstyleBehaviour } = ace.acequire('ace/mode/behaviour/cstyle'); -const { FoldMode: CStyleFoldMode } = ace.acequire('ace/mode/folding/cstyle'); - -const XJsonMode: any = function XJsonMode(this: any) { - const ruleset: any = new (XJsonHighlightRules as any)(); - ruleset.normalizeRules(); - this.$tokenizer = new AceTokenizer(ruleset.getRules()); - this.$outdent = new MatchingBraceOutdent(); - this.$behaviour = new CstyleBehaviour(); - this.foldingRules = new CStyleFoldMode(); -}; - -oop.inherits(XJsonMode, JSONMode); - -// Then clobber `createWorker` method to install our worker source. Per ace's wiki: https://github.com/ajaxorg/ace/wiki/Syntax-validation -(XJsonMode.prototype as any).createWorker = function (session: ace.IEditSession) { - const xJsonWorker = new WorkerClient(['ace'], workerModule, 'JsonWorker'); - - xJsonWorker.attachToDocument(session.getDocument()); - - xJsonWorker.on('annotate', function (e: { data: any }) { - session.setAnnotations(e.data); - }); - - xJsonWorker.on('terminate', function () { - session.clearAnnotations(); - }); - - return xJsonWorker; -}; - -export { XJsonMode }; - -export function installXJsonMode(editor: ace.Editor) { - const session = editor.getSession(); - session.setMode(new XJsonMode()); -} diff --git a/packages/kbn-ace/tsconfig.json b/packages/kbn-ace/tsconfig.json deleted file mode 100644 index a545abd7d65a6..0000000000000 --- a/packages/kbn-ace/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "allowJs": false, - "outDir": "target/types", - "stripInternal": true, - "types": ["node"] - }, - "include": [ - "**/*.ts", - "src/ace/modes/x_json/worker/x_json.ace.worker.js" - ], - "exclude": [ - "target/**/*", - ] -} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entity.ts index 4d522ef07ff0e..b26dbfc7ffb46 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/entity.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/entity.ts @@ -7,6 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +export type ObjectEntry = [keyof T, T[keyof T]]; + export type Fields | undefined = undefined> = { '@timestamp'?: number; } & (TMeta extends undefined ? {} : Partial<{ meta: TMeta }>); @@ -27,4 +29,14 @@ export class Entity { return this; } + + overrides(overrides: Partial) { + const overrideEntries = Object.entries(overrides) as Array>; + + overrideEntries.forEach(([fieldName, value]) => { + this.fields[fieldName] = value; + }); + + return this; + } } diff --git a/packages/kbn-apm-synthtrace-client/src/lib/gaussian_events.ts b/packages/kbn-apm-synthtrace-client/src/lib/gaussian_events.ts new file mode 100644 index 0000000000000..4f1db28017d29 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/gaussian_events.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { castArray } from 'lodash'; +import { SynthtraceGenerator } from '../types'; +import { Fields } from './entity'; +import { Serializable } from './serializable'; + +export class GaussianEvents { + constructor( + private readonly from: Date, + private readonly to: Date, + private readonly mean: Date, + private readonly width: number, + private readonly totalPoints: number + ) {} + + *generator( + map: ( + timestamp: number, + index: number + ) => Serializable | Array> + ): SynthtraceGenerator { + if (this.totalPoints <= 0) { + return; + } + + const startTime = this.from.getTime(); + const endTime = this.to.getTime(); + const meanTime = this.mean.getTime(); + const densityInterval = 1 / (this.totalPoints - 1); + + for (let eventIndex = 0; eventIndex < this.totalPoints; eventIndex++) { + const quantile = eventIndex * densityInterval; + + const standardScore = Math.sqrt(2) * inverseError(2 * quantile - 1); + const timestamp = Math.round(meanTime + standardScore * this.width); + + if (timestamp >= startTime && timestamp <= endTime) { + yield* this.generateEvents(timestamp, eventIndex, map); + } + } + } + + private *generateEvents( + timestamp: number, + eventIndex: number, + map: ( + timestamp: number, + index: number + ) => Serializable | Array> + ): Generator> { + const events = castArray(map(timestamp, eventIndex)); + for (const event of events) { + yield event; + } + } +} + +function inverseError(x: number): number { + const a = 0.147; + const sign = x < 0 ? -1 : 1; + + const part1 = 2 / (Math.PI * a) + Math.log(1 - x * x) / 2; + const part2 = Math.log(1 - x * x) / a; + + return sign * Math.sqrt(Math.sqrt(part1 * part1 - part2) - part1); +} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts index 198949b482be3..30550d64c4df8 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts @@ -27,7 +27,7 @@ interface HostDocument extends Fields { 'cloud.provider'?: string; } -class Host extends Entity { +export class Host extends Entity { cpu({ cpuTotalValue }: { cpuTotalValue?: number } = {}) { return new HostMetrics({ ...this.fields, @@ -175,3 +175,11 @@ export function host(name: string): Host { 'cloud.provider': 'gcp', }); } + +export function minimalHost(name: string): Host { + return new Host({ + 'agent.id': 'synthtrace', + 'host.hostname': name, + 'host.name': name, + }); +} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts index 853a9549ce02c..2957605cffcd3 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts @@ -8,7 +8,7 @@ */ import { dockerContainer, DockerContainerMetricsDocument } from './docker_container'; -import { host, HostMetricsDocument } from './host'; +import { host, HostMetricsDocument, minimalHost } from './host'; import { k8sContainer, K8sContainerMetricsDocument } from './k8s_container'; import { pod, PodMetricsDocument } from './pod'; import { awsRds, AWSRdsMetricsDocument } from './aws/rds'; @@ -24,6 +24,7 @@ export type InfraDocument = export const infra = { host, + minimalHost, pod, dockerContainer, k8sContainer, diff --git a/packages/kbn-apm-synthtrace-client/src/lib/interval.ts b/packages/kbn-apm-synthtrace-client/src/lib/interval.ts index 1d56c42e1fe12..5a5ed3ab5fdbe 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/interval.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/interval.ts @@ -34,6 +34,10 @@ interface IntervalOptions { rate?: number; } +interface StepDetails { + stepMilliseconds: number; +} + export class Interval { private readonly intervalAmount: number; private readonly intervalUnit: unitOfTime.DurationConstructor; @@ -46,12 +50,16 @@ export class Interval { this._rate = options.rate || 1; } + private getIntervalMilliseconds(): number { + return moment.duration(this.intervalAmount, this.intervalUnit).asMilliseconds(); + } + private getTimestamps() { const from = this.options.from.getTime(); const to = this.options.to.getTime(); let time: number = from; - const diff = moment.duration(this.intervalAmount, this.intervalUnit).asMilliseconds(); + const diff = this.getIntervalMilliseconds(); const timestamps: number[] = []; @@ -68,15 +76,19 @@ export class Interval { *generator( map: ( timestamp: number, - index: number + index: number, + stepDetails: StepDetails ) => Serializable | Array> ): SynthtraceGenerator { const timestamps = this.getTimestamps(); + const stepDetails: StepDetails = { + stepMilliseconds: this.getIntervalMilliseconds(), + }; let index = 0; for (const timestamp of timestamps) { - const events = castArray(map(timestamp, index)); + const events = castArray(map(timestamp, index, stepDetails)); index++; for (const event of events) { yield event; diff --git a/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts index e19f0f6fd6565..2bbc59eb37e70 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts @@ -68,6 +68,7 @@ export type LogDocument = Fields & 'event.duration': number; 'event.start': Date; 'event.end': Date; + labels?: Record; test_field: string | string[]; date: Date; severity: string; @@ -156,6 +157,26 @@ function create(logsOptions: LogsOptions = defaultLogsOptions): Log { ).dataset('synth'); } +function createMinimal({ + dataset = 'synth', + namespace = 'default', +}: { + dataset?: string; + namespace?: string; +} = {}): Log { + return new Log( + { + 'input.type': 'logs', + 'data_stream.namespace': namespace, + 'data_stream.type': 'logs', + 'data_stream.dataset': dataset, + 'event.dataset': dataset, + }, + { isLogsDb: false } + ); +} + export const log = { create, + createMinimal, }; diff --git a/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.test.ts b/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.test.ts new file mode 100644 index 0000000000000..0741884550f32 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.test.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { PoissonEvents } from './poisson_events'; +import { Serializable } from './serializable'; + +describe('poisson events', () => { + it('generates events within the given time range', () => { + const poissonEvents = new PoissonEvents(new Date(1000), new Date(2000), 10); + + const events = Array.from( + poissonEvents.generator((timestamp) => new Serializable({ '@timestamp': timestamp })) + ); + + expect(events.length).toBeGreaterThanOrEqual(1); + + for (const event of events) { + expect(event.fields['@timestamp']).toBeGreaterThanOrEqual(1000); + expect(event.fields['@timestamp']).toBeLessThanOrEqual(2000); + } + }); + + it('generates at least one event if the rate is greater than 0', () => { + const poissonEvents = new PoissonEvents(new Date(1000), new Date(2000), 1); + + const events = Array.from( + poissonEvents.generator((timestamp) => new Serializable({ '@timestamp': timestamp })) + ); + + expect(events.length).toBeGreaterThanOrEqual(1); + + for (const event of events) { + expect(event.fields['@timestamp']).toBeGreaterThanOrEqual(1000); + expect(event.fields['@timestamp']).toBeLessThanOrEqual(2000); + } + }); + + it('generates no event if the rate is 0', () => { + const poissonEvents = new PoissonEvents(new Date(1000), new Date(2000), 0); + + const events = Array.from( + poissonEvents.generator((timestamp) => new Serializable({ '@timestamp': timestamp })) + ); + + expect(events.length).toBe(0); + }); +}); diff --git a/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.ts b/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.ts new file mode 100644 index 0000000000000..e7fd24b8323e7 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { castArray } from 'lodash'; +import { SynthtraceGenerator } from '../types'; +import { Fields } from './entity'; +import { Serializable } from './serializable'; + +export class PoissonEvents { + constructor( + private readonly from: Date, + private readonly to: Date, + private readonly rate: number + ) {} + + private getTotalTimePeriod(): number { + return this.to.getTime() - this.from.getTime(); + } + + private getInterarrivalTime(): number { + const distribution = -Math.log(1 - Math.random()) / this.rate; + const totalTimePeriod = this.getTotalTimePeriod(); + return Math.floor(distribution * totalTimePeriod); + } + + *generator( + map: ( + timestamp: number, + index: number + ) => Serializable | Array> + ): SynthtraceGenerator { + if (this.rate <= 0) { + return; + } + + let currentTime = this.from.getTime(); + const endTime = this.to.getTime(); + let eventIndex = 0; + + while (currentTime < endTime) { + const interarrivalTime = this.getInterarrivalTime(); + currentTime += interarrivalTime; + + if (currentTime < endTime) { + yield* this.generateEvents(currentTime, eventIndex, map); + eventIndex++; + } + } + + // ensure at least one event has been emitted + if (this.rate > 0 && eventIndex === 0) { + const forcedEventTime = + this.from.getTime() + Math.floor(Math.random() * this.getTotalTimePeriod()); + yield* this.generateEvents(forcedEventTime, eventIndex, map); + } + } + + private *generateEvents( + timestamp: number, + eventIndex: number, + map: ( + timestamp: number, + index: number + ) => Serializable | Array> + ): Generator> { + const events = castArray(map(timestamp, eventIndex)); + for (const event of events) { + yield event; + } + } +} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts b/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts index ccdea4ee75197..1c6f12414a148 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts @@ -9,10 +9,12 @@ import datemath from '@kbn/datemath'; import type { Moment } from 'moment'; +import { GaussianEvents } from './gaussian_events'; import { Interval } from './interval'; +import { PoissonEvents } from './poisson_events'; export class Timerange { - constructor(private from: Date, private to: Date) {} + constructor(public readonly from: Date, public readonly to: Date) {} interval(interval: string) { return new Interval({ from: this.from, to: this.to, interval }); @@ -21,6 +23,29 @@ export class Timerange { ratePerMinute(rate: number) { return this.interval(`1m`).rate(rate); } + + poissonEvents(rate: number) { + return new PoissonEvents(this.from, this.to, rate); + } + + gaussianEvents(mean: Date, width: number, totalPoints: number) { + return new GaussianEvents(this.from, this.to, mean, width, totalPoints); + } + + splitInto(segmentCount: number): Timerange[] { + const duration = this.to.getTime() - this.from.getTime(); + const segmentDuration = duration / segmentCount; + + return Array.from({ length: segmentCount }, (_, i) => { + const from = new Date(this.from.getTime() + i * segmentDuration); + const to = new Date(from.getTime() + segmentDuration); + return new Timerange(from, to); + }); + } + + toString() { + return `Timerange(from=${this.from.toISOString()}, to=${this.to.toISOString()})`; + } } type DateLike = Date | number | Moment | string; diff --git a/packages/kbn-apm-synthtrace/src/lib/logs/custom_logsdb_index_templates.ts b/packages/kbn-apm-synthtrace/src/lib/logs/custom_logsdb_index_templates.ts index a0b155444919e..3eadd3f3941de 100644 --- a/packages/kbn-apm-synthtrace/src/lib/logs/custom_logsdb_index_templates.ts +++ b/packages/kbn-apm-synthtrace/src/lib/logs/custom_logsdb_index_templates.ts @@ -25,6 +25,7 @@ export const indexTemplates: { template: { settings: { mode: 'logsdb', + default_pipeline: 'logs@default-pipeline', }, }, priority: 500, diff --git a/packages/kbn-apm-synthtrace/src/lib/logs/logs_synthtrace_es_client.ts b/packages/kbn-apm-synthtrace/src/lib/logs/logs_synthtrace_es_client.ts index a6a64429f9b86..9673d1678132b 100644 --- a/packages/kbn-apm-synthtrace/src/lib/logs/logs_synthtrace_es_client.ts +++ b/packages/kbn-apm-synthtrace/src/lib/logs/logs_synthtrace_es_client.ts @@ -7,16 +7,20 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { Client } from '@elastic/elasticsearch'; +import { Client, estypes } from '@elastic/elasticsearch'; import { pipeline, Readable } from 'stream'; import { LogDocument } from '@kbn/apm-synthtrace-client/src/lib/logs'; -import { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types'; +import { IngestProcessorContainer, MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types'; +import { ValuesType } from 'utility-types'; import { SynthtraceEsClient, SynthtraceEsClientOptions } from '../shared/base_client'; import { getSerializeTransform } from '../shared/get_serialize_transform'; import { Logger } from '../utils/create_logger'; import { indexTemplates, IndexTemplateName } from './custom_logsdb_index_templates'; import { getRoutingTransform } from '../shared/data_stream_get_routing_transform'; +export const LogsIndex = 'logs'; +export const LogsCustom = 'logs@custom'; + export type LogsSynthtraceEsClientOptions = Omit; export class LogsSynthtraceEsClient extends SynthtraceEsClient { @@ -60,6 +64,47 @@ export class LogsSynthtraceEsClient extends SynthtraceEsClient { this.logger.error(`Index creation failed: ${index} - ${err.message}`); } } + + async updateIndexTemplate( + indexName: string, + modify: ( + template: ValuesType< + estypes.IndicesGetIndexTemplateResponse['index_templates'] + >['index_template'] + ) => estypes.IndicesPutIndexTemplateRequest + ) { + try { + const response = await this.client.indices.getIndexTemplate({ + name: indexName, + }); + + await Promise.all( + response.index_templates.map((template) => { + return this.client.indices.putIndexTemplate({ + ...modify(template.index_template), + name: template.name, + }); + }) + ); + + this.logger.info(`Updated ${indexName} index template`); + } catch (err) { + this.logger.error(`Update index template failed: ${indexName} - ${err.message}`); + } + } + + async createCustomPipeline(processors: IngestProcessorContainer[]) { + try { + this.client.ingest.putPipeline({ + id: LogsCustom, + processors, + version: 1, + }); + this.logger.info(`Custom pipeline created: ${LogsCustom}`); + } catch (err) { + this.logger.error(`Custom pipeline creation failed: ${LogsCustom} - ${err.message}`); + } + } } function logsPipeline() { diff --git a/packages/kbn-apm-synthtrace/src/scenarios/degraded_logs.ts b/packages/kbn-apm-synthtrace/src/scenarios/degraded_logs.ts index 47dd4ffd2652f..b3e41bbdd4e28 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/degraded_logs.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/degraded_logs.ts @@ -16,12 +16,10 @@ import { getCluster, getCloudRegion, getCloudProvider, + MORE_THAN_1024_CHARS, } from './helpers/logs_mock_data'; import { parseLogsScenarioOpts } from './helpers/logs_scenario_opts_parser'; -const MORE_THAN_1024_CHARS = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?'; - // Logs Data logic const MESSAGE_LOG_LEVELS = [ { message: 'A simple log', level: 'info' }, diff --git a/packages/kbn-apm-synthtrace/src/scenarios/degraded_synthetics_monitors.ts b/packages/kbn-apm-synthtrace/src/scenarios/degraded_synthetics_monitors.ts index c61fecd8b7109..6e00bfd0abf15 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/degraded_synthetics_monitors.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/degraded_synthetics_monitors.ts @@ -14,12 +14,9 @@ import { } from '@kbn/apm-synthtrace-client'; import { Scenario } from '../cli/scenario'; import { withClient } from '../lib/utils/with_client'; -import { getIpAddress } from './helpers/logs_mock_data'; +import { MORE_THAN_1024_CHARS, getIpAddress } from './helpers/logs_mock_data'; import { getAtIndexOrRandom } from './helpers/get_at_index_or_random'; -const MORE_THAN_1024_CHARS = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?'; - const MONITOR_NAMES = Array(4) .fill(null) .map((_, idx) => `synth-monitor-${idx}`); diff --git a/packages/kbn-apm-synthtrace/src/scenarios/distributed_unstructured_logs.ts b/packages/kbn-apm-synthtrace/src/scenarios/distributed_unstructured_logs.ts new file mode 100644 index 0000000000000..83860635ae64a --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/scenarios/distributed_unstructured_logs.ts @@ -0,0 +1,197 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { infra, LogDocument, log } from '@kbn/apm-synthtrace-client'; +import { fakerEN as faker } from '@faker-js/faker'; +import { z } from '@kbn/zod'; +import { Scenario } from '../cli/scenario'; +import { withClient } from '../lib/utils/with_client'; +import { + LogMessageGenerator, + generateUnstructuredLogMessage, + unstructuredLogMessageGenerators, +} from './helpers/unstructured_logs'; + +const scenarioOptsSchema = z.intersection( + z.object({ + randomSeed: z.number().default(0), + messageGroup: z + .enum([ + 'httpAccess', + 'userAuthentication', + 'networkEvent', + 'dbOperations', + 'taskOperations', + 'degradedOperations', + 'errorOperations', + ]) + .default('dbOperations'), + }), + z + .discriminatedUnion('distribution', [ + z.object({ + distribution: z.literal('uniform'), + rate: z.number().default(1), + }), + z.object({ + distribution: z.literal('poisson'), + rate: z.number().default(1), + }), + z.object({ + distribution: z.literal('gaussian'), + mean: z.coerce.date().describe('Time of the peak of the gaussian distribution'), + width: z.number().default(5000).describe('Width of the gaussian distribution in ms'), + totalPoints: z + .number() + .default(100) + .describe('Total number of points in the gaussian distribution'), + }), + ]) + .default({ distribution: 'uniform', rate: 1 }) +); + +type ScenarioOpts = z.output; + +const scenario: Scenario = async (runOptions) => { + return { + generate: ({ range, clients: { logsEsClient } }) => { + const { logger } = runOptions; + const scenarioOpts = scenarioOptsSchema.parse(runOptions.scenarioOpts ?? {}); + + faker.seed(scenarioOpts.randomSeed); + faker.setDefaultRefDate(range.from.toISOString()); + + logger.debug(`Generating ${scenarioOpts.distribution} logs...`); + + // Logs Data logic + const LOG_LEVELS = ['info', 'debug', 'error', 'warn', 'trace', 'fatal']; + + const clusterDefinions = [ + { + 'orchestrator.cluster.id': faker.string.nanoid(), + 'orchestrator.cluster.name': 'synth-cluster-1', + 'orchestrator.namespace': 'default', + 'cloud.provider': 'gcp', + 'cloud.region': 'eu-central-1', + 'cloud.availability_zone': 'eu-central-1a', + 'cloud.project.id': faker.string.nanoid(), + }, + { + 'orchestrator.cluster.id': faker.string.nanoid(), + 'orchestrator.cluster.name': 'synth-cluster-2', + 'orchestrator.namespace': 'production', + 'cloud.provider': 'aws', + 'cloud.region': 'us-east-1', + 'cloud.availability_zone': 'us-east-1a', + 'cloud.project.id': faker.string.nanoid(), + }, + { + 'orchestrator.cluster.id': faker.string.nanoid(), + 'orchestrator.cluster.name': 'synth-cluster-3', + 'orchestrator.namespace': 'kube', + 'cloud.provider': 'azure', + 'cloud.region': 'area-51', + 'cloud.availability_zone': 'area-51a', + 'cloud.project.id': faker.string.nanoid(), + }, + ]; + + const hostEntities = [ + { + 'host.name': 'host-1', + 'agent.id': 'synth-agent-1', + 'agent.name': 'nodejs', + 'cloud.instance.id': faker.string.nanoid(), + 'orchestrator.resource.id': faker.string.nanoid(), + ...clusterDefinions[0], + }, + { + 'host.name': 'host-2', + 'agent.id': 'synth-agent-2', + 'agent.name': 'custom', + 'cloud.instance.id': faker.string.nanoid(), + 'orchestrator.resource.id': faker.string.nanoid(), + ...clusterDefinions[1], + }, + { + 'host.name': 'host-3', + 'agent.id': 'synth-agent-3', + 'agent.name': 'python', + 'cloud.instance.id': faker.string.nanoid(), + 'orchestrator.resource.id': faker.string.nanoid(), + ...clusterDefinions[2], + }, + ].map((hostDefinition) => + infra.minimalHost(hostDefinition['host.name']).overrides(hostDefinition) + ); + + const serviceNames = Array(3) + .fill(null) + .map((_, idx) => `synth-service-${idx}`); + + const generatorFactory = + scenarioOpts.distribution === 'uniform' + ? range.interval('1s').rate(scenarioOpts.rate) + : scenarioOpts.distribution === 'poisson' + ? range.poissonEvents(scenarioOpts.rate) + : range.gaussianEvents(scenarioOpts.mean, scenarioOpts.width, scenarioOpts.totalPoints); + + const logs = generatorFactory.generator((timestamp) => { + const entity = faker.helpers.arrayElement(hostEntities); + const serviceName = faker.helpers.arrayElement(serviceNames); + const level = faker.helpers.arrayElement(LOG_LEVELS); + const messages = logMessageGenerators[scenarioOpts.messageGroup](faker); + + return messages.map((message) => + log + .createMinimal() + .message(message) + .logLevel(level) + .service(serviceName) + .overrides({ + ...entity.fields, + labels: { + scenario: 'rare', + population: scenarioOpts.distribution, + }, + }) + .timestamp(timestamp) + ); + }); + + return [ + withClient( + logsEsClient, + logger.perf('generating_logs', () => [logs]) + ), + ]; + }, + }; +}; + +export default scenario; + +const logMessageGenerators = { + httpAccess: generateUnstructuredLogMessage([unstructuredLogMessageGenerators.httpAccess]), + userAuthentication: generateUnstructuredLogMessage([ + unstructuredLogMessageGenerators.userAuthentication, + ]), + networkEvent: generateUnstructuredLogMessage([unstructuredLogMessageGenerators.networkEvent]), + dbOperations: generateUnstructuredLogMessage([unstructuredLogMessageGenerators.dbOperation]), + taskOperations: generateUnstructuredLogMessage([ + unstructuredLogMessageGenerators.taskStatusSuccess, + ]), + degradedOperations: generateUnstructuredLogMessage([ + unstructuredLogMessageGenerators.taskStatusFailure, + ]), + errorOperations: generateUnstructuredLogMessage([ + unstructuredLogMessageGenerators.error, + unstructuredLogMessageGenerators.restart, + ]), +} satisfies Record; diff --git a/packages/kbn-apm-synthtrace/src/scenarios/failed_logs.ts b/packages/kbn-apm-synthtrace/src/scenarios/failed_logs.ts new file mode 100644 index 0000000000000..91ddedac270b5 --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/scenarios/failed_logs.ts @@ -0,0 +1,195 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { LogDocument, log, generateShortId, generateLongId } from '@kbn/apm-synthtrace-client'; +import { merge } from 'lodash'; +import { Scenario } from '../cli/scenario'; +import { IndexTemplateName } from '../lib/logs/custom_logsdb_index_templates'; +import { withClient } from '../lib/utils/with_client'; +import { + getServiceName, + getCluster, + getCloudRegion, + getCloudProvider, + MORE_THAN_1024_CHARS, +} from './helpers/logs_mock_data'; +import { parseLogsScenarioOpts } from './helpers/logs_scenario_opts_parser'; +import { LogsIndex } from '../lib/logs/logs_synthtrace_es_client'; + +const processors = [ + { + script: { + tag: 'normalize log level', + lang: 'painless', + source: ` + String level = ctx['log.level']; + if ('0'.equals(level)) { + ctx['log.level'] = 'info'; + } else if ('1'.equals(level)) { + ctx['log.level'] = 'debug'; + } else if ('2'.equals(level)) { + ctx['log.level'] = 'warning'; + } else if ('3'.equals(level)) { + ctx['log.level'] = 'error'; + } else { + throw new Exception("Not a valid log level"); + } + `, + }, + }, +]; + +// Logs Data logic +const MESSAGE_LOG_LEVELS = [ + { message: 'A simple log', level: '0' }, + { + message: 'Another log message', + level: '1', + }, + { + message: 'A log message generated from a warning', + level: '2', + }, + { message: 'Error with certificate: "ca_trusted_fingerprint"', level: '3' }, +]; + +const scenario: Scenario = async (runOptions) => { + const { isLogsDb } = parseLogsScenarioOpts(runOptions.scenarioOpts); + return { + bootstrap: async ({ logsEsClient }) => { + await logsEsClient.createCustomPipeline(processors); + if (isLogsDb) await logsEsClient.createIndexTemplate(IndexTemplateName.LogsDb); + + await logsEsClient.updateIndexTemplate( + isLogsDb ? IndexTemplateName.LogsDb : LogsIndex, + (template) => { + const next = { + name: LogsIndex, + data_stream: { + failure_store: true, + }, + }; + + return merge({}, template, next); + } + ); + }, + generate: ({ range, clients: { logsEsClient } }) => { + const { logger } = runOptions; + + const constructLogsCommonData = () => { + const index = Math.floor(Math.random() * 3); + const serviceName = getServiceName(index); + const logMessage = MESSAGE_LOG_LEVELS[index]; + const { clusterId, clusterName } = getCluster(index); + const cloudRegion = getCloudRegion(index); + + const commonLongEntryFields: LogDocument = { + 'trace.id': generateShortId(), + 'agent.name': 'synth-agent', + 'orchestrator.cluster.name': clusterName, + 'orchestrator.cluster.id': clusterId, + 'orchestrator.resource.id': generateShortId(), + 'cloud.provider': getCloudProvider(), + 'cloud.region': cloudRegion, + 'cloud.availability_zone': `${cloudRegion}a`, + 'cloud.project.id': generateShortId(), + 'cloud.instance.id': generateShortId(), + 'log.file.path': `/logs/${generateLongId()}/error.txt`, + }; + + return { + index, + serviceName, + logMessage, + cloudRegion, + commonLongEntryFields, + }; + }; + + const datasetSynth1Logs = (timestamp: number) => { + const { + serviceName, + logMessage: { level, message }, + commonLongEntryFields, + } = constructLogsCommonData(); + + return log + .create({ isLogsDb }) + .dataset('synth.1') + .message(message) + .logLevel(level) + .service(serviceName) + .defaults(commonLongEntryFields) + .timestamp(timestamp); + }; + + const datasetSynth2Logs = (i: number, timestamp: number) => { + const { + serviceName, + logMessage: { level, message }, + commonLongEntryFields, + } = constructLogsCommonData(); + const isFailed = i % 60 === 0; + return log + .create({ isLogsDb }) + .dataset('synth.2') + .message(message) + .logLevel(isFailed ? '4' : level) // "script_exception": Not a valid log level + .service(serviceName) + .defaults(commonLongEntryFields) + .timestamp(timestamp); + }; + + const datasetSynth3Logs = (i: number, timestamp: number) => { + const { + serviceName, + logMessage: { level, message }, + cloudRegion, + commonLongEntryFields, + } = constructLogsCommonData(); + const isMalformed = i % 10 === 0; + const isFailed = i % 80 === 0; + return log + .create({ isLogsDb }) + .dataset('synth.3') + .message(message) + .logLevel(isFailed ? '5' : level) // "script_exception": Not a valid log level + .service(serviceName) + .defaults({ + ...commonLongEntryFields, + 'cloud.availability_zone': isMalformed + ? MORE_THAN_1024_CHARS // "ignore_above": 1024 in mapping + : `${cloudRegion}a`, + }) + .timestamp(timestamp); + }; + + const logs = range + .interval('1m') + .rate(1) + .generator((timestamp) => { + return Array(200) + .fill(0) + .flatMap((_, index) => [ + datasetSynth1Logs(timestamp), + datasetSynth2Logs(index, timestamp), + datasetSynth3Logs(index, timestamp), + ]); + }); + + return withClient( + logsEsClient, + logger.perf('generating_logs', () => logs) + ); + }, + }; +}; + +export default scenario; diff --git a/packages/kbn-apm-synthtrace/src/scenarios/helpers/logs_mock_data.ts b/packages/kbn-apm-synthtrace/src/scenarios/helpers/logs_mock_data.ts index e974528f16a80..5f3cbd5f054dd 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/helpers/logs_mock_data.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/helpers/logs_mock_data.ts @@ -59,6 +59,9 @@ const SERVICE_NAMES = Array(3) .fill(null) .map((_, idx) => `synth-service-${idx}`); +export const MORE_THAN_1024_CHARS = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?'; + // Functions to get random elements export const getCluster = (index?: number) => getAtIndexOrRandom(CLUSTER, index); export const getIpAddress = (index?: number) => getAtIndexOrRandom(IP_ADDRESSES, index); diff --git a/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.ts b/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.ts new file mode 100644 index 0000000000000..490bd449e2b60 --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { Faker, faker } from '@faker-js/faker'; + +export type LogMessageGenerator = (f: Faker) => string[]; + +export const unstructuredLogMessageGenerators = { + httpAccess: (f: Faker) => [ + `${f.internet.ip()} - - [${f.date + .past() + .toISOString() + .replace('T', ' ') + .replace( + /\..+/, + '' + )}] "${f.internet.httpMethod()} ${f.internet.url()} HTTP/1.1" ${f.helpers.arrayElement([ + 200, 301, 404, 500, + ])} ${f.number.int({ min: 100, max: 5000 })}`, + ], + dbOperation: (f: Faker) => [ + `${f.database.engine()}: ${f.database.column()} ${f.helpers.arrayElement([ + 'created', + 'updated', + 'deleted', + 'inserted', + ])} successfully ${f.number.int({ max: 100000 })} times`, + ], + taskStatusSuccess: (f: Faker) => [ + `${f.hacker.noun()}: ${f.word.words()} ${f.helpers.arrayElement([ + 'triggered', + 'executed', + 'processed', + 'handled', + ])} successfully at ${f.date.recent().toISOString()}`, + ], + taskStatusFailure: (f: Faker) => [ + `${f.hacker.noun()}: ${f.helpers.arrayElement([ + 'triggering', + 'execution', + 'processing', + 'handling', + ])} of ${f.word.words()} failed at ${f.date.recent().toISOString()}`, + ], + error: (f: Faker) => [ + `${f.helpers.arrayElement([ + 'Error', + 'Exception', + 'Failure', + 'Crash', + 'Bug', + 'Issue', + ])}: ${f.hacker.phrase()}`, + `Stopping ${f.number.int(42)} background tasks...`, + 'Shutting down process...', + ], + restart: (f: Faker) => { + const service = f.database.engine(); + return [ + `Restarting ${service}...`, + `Waiting for queue to drain...`, + `Service ${service} restarted ${f.helpers.arrayElement([ + 'successfully', + 'with errors', + 'with warnings', + ])}`, + ]; + }, + userAuthentication: (f: Faker) => [ + `User ${f.internet.userName()} ${f.helpers.arrayElement([ + 'logged in', + 'logged out', + 'failed to login', + ])}`, + ], + networkEvent: (f: Faker) => [ + `Network ${f.helpers.arrayElement([ + 'connection', + 'disconnection', + 'data transfer', + ])} ${f.helpers.arrayElement(['from', 'to'])} ${f.internet.ip()}`, + ], +} satisfies Record; + +export const generateUnstructuredLogMessage = + (generators: LogMessageGenerator[] = Object.values(unstructuredLogMessageGenerators)) => + (f: Faker = faker) => + f.helpers.arrayElement(generators)(f); diff --git a/packages/kbn-apm-synthtrace/src/scenarios/logs_traces_hosts.ts b/packages/kbn-apm-synthtrace/src/scenarios/logs_traces_hosts.ts index 8a6bdf409a573..6dac3fc9f3226 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/logs_traces_hosts.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/logs_traces_hosts.ts @@ -8,21 +8,22 @@ */ import { - log, - LogDocument, + ApmFields, InfraDocument, - apm, Instance, - infra, - ApmFields, + LogDocument, + apm, generateShortId, + infra, + log, } from '@kbn/apm-synthtrace-client'; import { Scenario } from '../cli/scenario'; +import { IndexTemplateName } from '../lib/logs/custom_logsdb_index_templates'; import { Logger } from '../lib/utils/create_logger'; -import { withClient } from '../lib/utils/with_client'; import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment'; +import { withClient } from '../lib/utils/with_client'; +import { MORE_THAN_1024_CHARS } from './helpers/logs_mock_data'; import { parseLogsScenarioOpts, parseStringToBoolean } from './helpers/logs_scenario_opts_parser'; -import { IndexTemplateName } from '../lib/logs/custom_logsdb_index_templates'; const ENVIRONMENT = getSynthtraceEnvironment(__filename); @@ -475,6 +476,3 @@ const DATASETS = [ ]; const LOG_LEVELS = ['info', 'error', 'warn', 'debug']; - -const MORE_THAN_1024_CHARS = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?'; diff --git a/packages/kbn-apm-synthtrace/src/scenarios/simple_logs.ts b/packages/kbn-apm-synthtrace/src/scenarios/simple_logs.ts index 3c1fdc5131395..08d914c1017dd 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/simple_logs.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/simple_logs.ts @@ -7,19 +7,20 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { LogDocument, log, generateShortId, generateLongId } from '@kbn/apm-synthtrace-client'; +import { LogDocument, generateLongId, generateShortId, log } from '@kbn/apm-synthtrace-client'; import moment from 'moment'; import { Scenario } from '../cli/scenario'; import { IndexTemplateName } from '../lib/logs/custom_logsdb_index_templates'; import { withClient } from '../lib/utils/with_client'; import { - getServiceName, - getGeoCoordinate, - getIpAddress, - getCluster, + MORE_THAN_1024_CHARS, + getAgentName, getCloudProvider, getCloudRegion, - getAgentName, + getCluster, + getGeoCoordinate, + getIpAddress, + getServiceName, } from './helpers/logs_mock_data'; import { parseLogsScenarioOpts } from './helpers/logs_scenario_opts_parser'; @@ -30,9 +31,6 @@ const MESSAGE_LOG_LEVELS = [ { message: 'Error with certificate: "ca_trusted_fingerprint"', level: 'error' }, ]; -const MORE_THAN_1024_CHARS = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?'; - const scenario: Scenario = async (runOptions) => { const { isLogsDb } = parseLogsScenarioOpts(runOptions.scenarioOpts); diff --git a/packages/kbn-apm-synthtrace/tsconfig.json b/packages/kbn-apm-synthtrace/tsconfig.json index d0f5c5801597a..db93e36421b83 100644 --- a/packages/kbn-apm-synthtrace/tsconfig.json +++ b/packages/kbn-apm-synthtrace/tsconfig.json @@ -10,6 +10,7 @@ "@kbn/apm-synthtrace-client", "@kbn/dev-utils", "@kbn/elastic-agent-utils", + "@kbn/zod", ], "exclude": [ "target/**/*", diff --git a/packages/kbn-esql-ast/src/pretty_print/__tests__/wrapping_pretty_printer.test.ts b/packages/kbn-esql-ast/src/pretty_print/__tests__/wrapping_pretty_printer.test.ts index 21330d0fea3b1..2dfe239ce5b88 100644 --- a/packages/kbn-esql-ast/src/pretty_print/__tests__/wrapping_pretty_printer.test.ts +++ b/packages/kbn-esql-ast/src/pretty_print/__tests__/wrapping_pretty_printer.test.ts @@ -593,6 +593,85 @@ ROW (asdf + asdf)::string, 1.2::string, "1234"::integer, (12321342134 + 23412341 - "aaaaaaaaaaa")::boolean`); }); }); + + describe('list literals', () => { + describe('numeric', () => { + test('wraps long list literals one line', () => { + const query = + 'ROW [1234567890, 1234567890, 1234567890, 1234567890, 1234567890, 1234567890, 1234567890, 1234567890, 1234567890]'; + const text = reprint(query).text; + + expect('\n' + text).toBe(` +ROW + [1234567890, 1234567890, 1234567890, 1234567890, 1234567890, 1234567890, + 1234567890, 1234567890, 1234567890]`); + }); + + test('wraps long list literals to multiple lines one line', () => { + const query = `ROW [1234567890, 1234567890, 1234567890, 1234567890, 1234567890, 1234567890, + 1234567890, 1234567890, 1234567890, 1234567890, 1234567890, 1234567890, + 1234567890, 1234567890, 1234567890, 1234567890, 1234567890, 1234567890, + 1234567890, 1234567890, 1234567890]`; + const text = reprint(query).text; + + expect('\n' + text).toBe(` +ROW + [1234567890, 1234567890, 1234567890, 1234567890, 1234567890, 1234567890, + 1234567890, 1234567890, 1234567890, 1234567890, 1234567890, 1234567890, + 1234567890, 1234567890, 1234567890, 1234567890, 1234567890, 1234567890, + 1234567890, 1234567890, 1234567890]`); + }); + + test('breaks very long values one-per-line', () => { + const query = `ROW fn1(fn2(fn3(fn4(fn5(fn6(fn7(fn8([1234567890, 1234567890, 1234567890, 1234567890, 1234567890]))))))))`; + const text = reprint(query, { wrap: 40 }).text; + + expect('\n' + text).toBe(` +ROW + FN1( + FN2( + FN3( + FN4( + FN5( + FN6( + FN7( + FN8( + [ + 1234567890, + 1234567890, + 1234567890, + 1234567890, + 1234567890]))))))))`); + }); + }); + + describe('string', () => { + test('wraps long list literals one line', () => { + const query = + 'ROW ["some text", "another text", "one more text literal", "and another one", "and one more", "and one more", "and one more", "and one more", "and one more"]'; + const text = reprint(query).text; + + expect('\n' + text).toBe(` +ROW + ["some text", "another text", "one more text literal", "and another one", + "and one more", "and one more", "and one more", "and one more", + "and one more"]`); + }); + + test('can break very long strings per line', () => { + const query = + 'ROW ["..............................................", "..............................................", ".............................................."]'; + const text = reprint(query).text; + + expect('\n' + text).toBe(` +ROW + [ + "..............................................", + "..............................................", + ".............................................."]`); + }); + }); + }); }); test.todo('Idempotence on multiple times pretty printing'); diff --git a/packages/kbn-esql-ast/src/pretty_print/wrapping_pretty_printer.ts b/packages/kbn-esql-ast/src/pretty_print/wrapping_pretty_printer.ts index fde7f60a1dba5..91f65a389f0c3 100644 --- a/packages/kbn-esql-ast/src/pretty_print/wrapping_pretty_printer.ts +++ b/packages/kbn-esql-ast/src/pretty_print/wrapping_pretty_printer.ts @@ -15,9 +15,10 @@ import { CommandVisitorContext, ExpressionVisitorContext, FunctionCallExpressionVisitorContext, + ListLiteralExpressionVisitorContext, Visitor, } from '../visitor'; -import { singleItems } from '../visitor/utils'; +import { children, singleItems } from '../visitor/utils'; import { BasicPrettyPrinter, BasicPrettyPrinterOptions } from './basic_pretty_printer'; import { getPrettyPrintStats } from './helpers'; import { LeafPrinter } from './leaf_printer'; @@ -235,7 +236,11 @@ export class WrappingPrettyPrinter { } private printArguments( - ctx: CommandVisitorContext | CommandOptionVisitorContext | FunctionCallExpressionVisitorContext, + ctx: + | CommandVisitorContext + | CommandOptionVisitorContext + | FunctionCallExpressionVisitorContext + | ListLiteralExpressionVisitorContext, inp: Input ) { let txt = ''; @@ -247,7 +252,7 @@ export class WrappingPrettyPrinter { let remainingCurrentLine = inp.remaining; let oneArgumentPerLine = false; - for (const child of singleItems(ctx.node.args)) { + for (const child of children(ctx.node)) { if (getPrettyPrintStats(child).hasLineBreakingDecorations) { oneArgumentPerLine = true; break; @@ -489,13 +494,11 @@ export class WrappingPrettyPrinter { }) .on('visitListLiteralExpression', (ctx, inp: Input): Output => { - let elements = ''; - - for (const out of ctx.visitElements(inp)) { - elements += (elements ? ', ' : '') + out.txt; - } - - const formatted = `[${elements}]${inp.suffix ?? ''}`; + const args = this.printArguments(ctx, { + indent: inp.indent, + remaining: inp.remaining - 1, + }); + const formatted = `[${args.txt}]${inp.suffix ?? ''}`; const { txt, indented } = this.decorateWithComments(inp.indent, ctx.node, formatted); return { txt, indented }; diff --git a/packages/kbn-esql-ast/src/types.ts b/packages/kbn-esql-ast/src/types.ts index 0ca48b2326f7d..1bac6e0cff5b3 100644 --- a/packages/kbn-esql-ast/src/types.ts +++ b/packages/kbn-esql-ast/src/types.ts @@ -40,6 +40,7 @@ export type ESQLAstField = ESQLFunction | ESQLColumn; export type ESQLAstItem = ESQLSingleAstItem | ESQLAstItem[]; export type ESQLAstNodeWithArgs = ESQLCommand | ESQLCommandOption | ESQLFunction; +export type ESQLAstNodeWithChildren = ESQLAstNodeWithArgs | ESQLList; /** * *Proper* are nodes which are objects with `type` property, once we get rid diff --git a/packages/kbn-esql-ast/src/visitor/contexts.ts b/packages/kbn-esql-ast/src/visitor/contexts.ts index 0f637962b7ddd..4b4f04fdca4bb 100644 --- a/packages/kbn-esql-ast/src/visitor/contexts.ts +++ b/packages/kbn-esql-ast/src/visitor/contexts.ts @@ -12,11 +12,12 @@ // and makes it harder to understand the code structure. import { type GlobalVisitorContext, SharedData } from './global_visitor_context'; -import { firstItem, singleItems } from './utils'; +import { children, firstItem, singleItems } from './utils'; import type { ESQLAstCommand, ESQLAstItem, ESQLAstNodeWithArgs, + ESQLAstNodeWithChildren, ESQLAstRenameExpression, ESQLColumn, ESQLCommandOption, @@ -47,6 +48,11 @@ import { Builder } from '../builder'; const isNodeWithArgs = (x: unknown): x is ESQLAstNodeWithArgs => !!x && typeof x === 'object' && Array.isArray((x as any).args); +const isNodeWithChildren = (x: unknown): x is ESQLAstNodeWithChildren => + !!x && + typeof x === 'object' && + (Array.isArray((x as any).args) || Array.isArray((x as any).values)); + export class VisitorContext< Methods extends VisitorMethods = VisitorMethods, Data extends SharedData = SharedData, @@ -99,13 +105,13 @@ export class VisitorContext< public arguments(): ESQLAstExpressionNode[] { const node = this.node; - if (!isNodeWithArgs(node)) { + if (!isNodeWithChildren(node)) { return []; } const args: ESQLAstExpressionNode[] = []; - for (const arg of singleItems(node.args)) { + for (const arg of children(node)) { args.push(arg); } diff --git a/packages/kbn-esql-ast/src/visitor/utils.ts b/packages/kbn-esql-ast/src/visitor/utils.ts index 2e54a89c2bf52..0dc95b73cf9d7 100644 --- a/packages/kbn-esql-ast/src/visitor/utils.ts +++ b/packages/kbn-esql-ast/src/visitor/utils.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { ESQLAstItem, ESQLSingleAstItem } from '../types'; +import { ESQLAstItem, ESQLProperNode, ESQLSingleAstItem } from '../types'; /** * Normalizes AST "item" list to only contain *single* items. @@ -48,3 +48,32 @@ export const lastItem = (items: ESQLAstItem[]): ESQLSingleAstItem | undefined => if (Array.isArray(last)) return lastItem(last as ESQLAstItem[]); return last as ESQLSingleAstItem; }; + +export function* children(node: ESQLProperNode): Iterable { + switch (node.type) { + case 'function': + case 'command': + case 'option': { + for (const arg of singleItems(node.args)) { + yield arg; + } + break; + } + case 'list': { + for (const item of singleItems(node.values)) { + yield item; + } + break; + } + case 'inlineCast': { + if (Array.isArray(node.value)) { + for (const item of singleItems(node.value)) { + yield item; + } + } else { + yield node.value; + } + break; + } + } +} diff --git a/packages/kbn-esql-utils/index.ts b/packages/kbn-esql-utils/index.ts index 223181f2bd154..333557964d873 100644 --- a/packages/kbn-esql-utils/index.ts +++ b/packages/kbn-esql-utils/index.ts @@ -29,6 +29,7 @@ export { isQueryWrappedByPipes, retrieveMetadataColumns, getQueryColumnsFromESQLQuery, + isESQLColumnSortable, TextBasedLanguages, } from './src'; diff --git a/packages/kbn-esql-utils/src/index.ts b/packages/kbn-esql-utils/src/index.ts index e36283c7a9238..3b3228e7a2a4a 100644 --- a/packages/kbn-esql-utils/src/index.ts +++ b/packages/kbn-esql-utils/src/index.ts @@ -31,3 +31,4 @@ export { getStartEndParams, hasStartEndParams, } from './utils/run_query'; +export { isESQLColumnSortable } from './utils/esql_fields_utils'; diff --git a/packages/kbn-esql-utils/src/utils/esql_fields_utils.test.ts b/packages/kbn-esql-utils/src/utils/esql_fields_utils.test.ts new file mode 100644 index 0000000000000..ef8a24e686bd6 --- /dev/null +++ b/packages/kbn-esql-utils/src/utils/esql_fields_utils.test.ts @@ -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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ +import type { DatatableColumn } from '@kbn/expressions-plugin/common'; +import { isESQLColumnSortable } from './esql_fields_utils'; + +describe('esql fields helpers', () => { + describe('isESQLColumnSortable', () => { + it('returns false for geo fields', () => { + const geoField = { + id: 'geo.coordinates', + name: 'geo.coordinates', + meta: { + type: 'geo_point', + esType: 'geo_point', + }, + isNull: false, + } as DatatableColumn; + expect(isESQLColumnSortable(geoField)).toBeFalsy(); + }); + + it('returns false for source fields', () => { + const sourceField = { + id: '_source', + name: '_source', + meta: { + type: '_source', + esType: '_source', + }, + isNull: false, + } as DatatableColumn; + expect(isESQLColumnSortable(sourceField)).toBeFalsy(); + }); + + it('returns false for counter fields', () => { + const tsdbField = { + id: 'tsbd_counter', + name: 'tsbd_counter', + meta: { + type: 'number', + esType: 'counter_long', + }, + isNull: false, + } as DatatableColumn; + expect(isESQLColumnSortable(tsdbField)).toBeFalsy(); + }); + + it('returns true for everything else', () => { + const keywordField = { + id: 'sortable', + name: 'sortable', + meta: { + type: 'string', + esType: 'keyword', + }, + isNull: false, + } as DatatableColumn; + expect(isESQLColumnSortable(keywordField)).toBeTruthy(); + }); + }); +}); diff --git a/packages/kbn-esql-utils/src/utils/esql_fields_utils.ts b/packages/kbn-esql-utils/src/utils/esql_fields_utils.ts new file mode 100644 index 0000000000000..f5a0fe7b81340 --- /dev/null +++ b/packages/kbn-esql-utils/src/utils/esql_fields_utils.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { DatatableColumn } from '@kbn/expressions-plugin/common'; + +const SPATIAL_FIELDS = ['geo_point', 'geo_shape', 'point', 'shape']; +const SOURCE_FIELD = '_source'; +const TSDB_COUNTER_FIELDS_PREFIX = 'counter_'; + +/** + * Check if a column is sortable. + * + * @param column The DatatableColumn of the field. + * @returns True if the column is sortable, false otherwise. + */ + +export const isESQLColumnSortable = (column: DatatableColumn): boolean => { + // We don't allow sorting on spatial fields + if (SPATIAL_FIELDS.includes(column.meta?.type)) { + return false; + } + + // we don't allow sorting on the _source field + if (column.meta?.type === SOURCE_FIELD) { + return false; + } + + // we don't allow sorting on tsdb counter fields + if (column.meta?.esType && column.meta?.esType?.indexOf(TSDB_COUNTER_FIELDS_PREFIX) !== -1) { + return false; + } + + return true; +}; diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts index 84779f1dd36b5..a0a4a359c5ff6 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts @@ -387,6 +387,23 @@ describe('autocomplete', () => { '```````````````````````````````round(doubleField) + 1```````````````` + 1```````` + 1```` + 1`` + 1`', ] ); + + it('should not suggest already-used fields and variables', async () => { + const { suggest: suggestTest } = await setup(); + const getSuggestions = async (query: string) => + (await suggestTest(query)).map((value) => value.text); + + expect(await getSuggestions('from a_index | EVAL foo = 1 | KEEP /')).toContain('foo'); + expect(await getSuggestions('from a_index | EVAL foo = 1 | KEEP foo, /')).not.toContain( + 'foo' + ); + expect(await getSuggestions('from a_index | EVAL foo = 1 | KEEP /')).toContain( + 'doubleField' + ); + expect( + await getSuggestions('from a_index | EVAL foo = 1 | KEEP doubleField, /') + ).not.toContain('doubleField'); + }); }); } @@ -1111,11 +1128,14 @@ describe('autocomplete', () => { ]); }); - describe('KEEP ', () => { + describe.each(['KEEP', 'DROP'])('%s ', (commandName) => { // KEEP field - testSuggestions('FROM a | KEEP /', getFieldNamesByType('any').map(attachTriggerCommand)); testSuggestions( - 'FROM a | KEEP d/', + `FROM a | ${commandName} /`, + getFieldNamesByType('any').map(attachTriggerCommand) + ); + testSuggestions( + `FROM a | ${commandName} d/`, getFieldNamesByType('any') .map((text) => ({ text, @@ -1124,11 +1144,11 @@ describe('autocomplete', () => { .map(attachTriggerCommand) ); testSuggestions( - 'FROM a | KEEP doubleFiel/', + `FROM a | ${commandName} doubleFiel/`, getFieldNamesByType('any').map(attachTriggerCommand) ); testSuggestions( - 'FROM a | KEEP doubleField/', + `FROM a | ${commandName} doubleField/`, ['doubleField, ', 'doubleField | '] .map((text) => ({ text, @@ -1141,7 +1161,7 @@ describe('autocomplete', () => { // Let's get funky with the field names testSuggestions( - 'FROM a | KEEP @timestamp/', + `FROM a | ${commandName} @timestamp/`, ['@timestamp, ', '@timestamp | '] .map((text) => ({ text, @@ -1150,10 +1170,15 @@ describe('autocomplete', () => { })) .map(attachTriggerCommand), undefined, - [[{ name: '@timestamp', type: 'date' }]] + [ + [ + { name: '@timestamp', type: 'date' }, + { name: 'utc_stamp', type: 'date' }, + ], + ] ); testSuggestions( - 'FROM a | KEEP foo.bar/', + `FROM a | ${commandName} foo.bar/`, ['foo.bar, ', 'foo.bar | '] .map((text) => ({ text, @@ -1162,26 +1187,34 @@ describe('autocomplete', () => { })) .map(attachTriggerCommand), undefined, - [[{ name: 'foo.bar', type: 'double' }]] + [ + [ + { name: 'foo.bar', type: 'double' }, + { name: 'baz', type: 'date' }, + ], + ] ); describe('escaped field names', () => { // This isn't actually the behavior we want, but this test is here // to make sure no weird suggestions start cropping up in this case. - testSuggestions('FROM a | KEEP `foo.bar`/', ['foo.bar'], undefined, [ + testSuggestions(`FROM a | ${commandName} \`foo.bar\`/`, ['foo.bar'], undefined, [ [{ name: 'foo.bar', type: 'double' }], ]); // @todo re-enable these tests when we can use AST to support this case - testSuggestions.skip('FROM a | KEEP `foo.bar`/', ['foo.bar, ', 'foo.bar | '], undefined, [ - [{ name: 'foo.bar', type: 'double' }], - ]); testSuggestions.skip( - 'FROM a | KEEP `foo`.`bar`/', + `FROM a | ${commandName} \`foo.bar\`/`, ['foo.bar, ', 'foo.bar | '], undefined, [[{ name: 'foo.bar', type: 'double' }]] ); - testSuggestions.skip('FROM a | KEEP `any#Char$Field`/', [ + testSuggestions.skip( + `FROM a | ${commandName} \`foo\`.\`bar\`/`, + ['foo.bar, ', 'foo.bar | '], + undefined, + [[{ name: 'foo.bar', type: 'double' }]] + ); + testSuggestions.skip(`FROM a | ${commandName} \`any#Char$Field\`/`, [ '`any#Char$Field`, ', '`any#Char$Field` | ', ]); @@ -1189,12 +1222,28 @@ describe('autocomplete', () => { // Subsequent fields testSuggestions( - 'FROM a | KEEP doubleField, dateFiel/', + `FROM a | ${commandName} doubleField, dateFiel/`, getFieldNamesByType('any') .filter((s) => s !== 'doubleField') .map(attachTriggerCommand) ); - testSuggestions('FROM a | KEEP doubleField, dateField/', ['dateField, ', 'dateField | ']); + testSuggestions(`FROM a | ${commandName} doubleField, dateField/`, [ + 'dateField, ', + 'dateField | ', + ]); + + // out of fields + testSuggestions( + `FROM a | ${commandName} doubleField, dateField/`, + ['dateField | '], + undefined, + [ + [ + { name: 'doubleField', type: 'double' }, + { name: 'dateField', type: 'date' }, + ], + ] + ); }); }); }); diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts index 2433f5d496521..6f9fb66a8c715 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts @@ -627,7 +627,7 @@ async function getExpressionSuggestionsByType( literals: argDef.constantOnly, }, { - ignoreFields: isNewExpression + ignoreColumns: isNewExpression ? command.args.filter(isColumnItem).map(({ name }) => name) : [], } @@ -656,10 +656,15 @@ async function getExpressionSuggestionsByType( })); } - return [ - { ...pipeCompleteItem, text: ' | ' }, - { ...commaCompleteItem, text: ', ' }, - ].map((s) => ({ + const finalSuggestions = [{ ...pipeCompleteItem, text: ' | ' }]; + if (fieldSuggestions.length > 1) + // when we fix the editor marker, this should probably be checked against 0 instead of 1 + // this is because the last field in the AST is currently getting removed (because it contains + // the editor marker) so it is not included in the ignored list which is used to filter out + // existing fields above. + finalSuggestions.push({ ...commaCompleteItem, text: ', ' }); + + return finalSuggestions.map((s) => ({ ...s, filterText: fragment, text: fragment + s.text, @@ -1176,15 +1181,15 @@ async function getFieldsOrFunctionsSuggestions( }, { ignoreFn = [], - ignoreFields = [], + ignoreColumns = [], }: { ignoreFn?: string[]; - ignoreFields?: string[]; + ignoreColumns?: string[]; } = {} ): Promise { const filteredFieldsByType = pushItUpInTheList( (await (fields - ? getFieldsByType(types, ignoreFields, { + ? getFieldsByType(types, ignoreColumns, { advanceCursor: commandName === 'sort', openSuggestions: commandName === 'sort', }) @@ -1195,7 +1200,10 @@ async function getFieldsOrFunctionsSuggestions( const filteredVariablesByType: string[] = []; if (variables) { for (const variable of variables.values()) { - if (types.includes('any') || types.includes(variable[0].type)) { + if ( + (types.includes('any') || types.includes(variable[0].type)) && + !ignoreColumns.includes(variable[0].name) + ) { filteredVariablesByType.push(variable[0].name); } } @@ -1515,7 +1523,7 @@ async function getListArgsSuggestions( fields: true, variables: anyVariables, }, - { ignoreFields: [firstArg.name, ...otherArgs.map(({ name }) => name)] } + { ignoreColumns: [firstArg.name, ...otherArgs.map(({ name }) => name)] } )) ); } @@ -1875,18 +1883,16 @@ async function getOptionArgsSuggestions( * for a given fragment of text in a generic way. A good example is * a field name. * - * When typing a field name, there are three scenarios + * When typing a field name, there are 2 scenarios * - * 1. user hasn't begun typing + * 1. field name is incomplete (includes the empty string) * KEEP / - * - * 2. user is typing a partial field name * KEEP fie/ * - * 3. user has typed a complete field name + * 2. field name is complete * KEEP field/ * - * This function provides a framework for handling all three scenarios in a clean way. + * This function provides a framework for detecting and handling both scenarios in a clean way. * * @param innerText - the query text before the current cursor position * @param isFragmentComplete — return true if the fragment is complete diff --git a/packages/kbn-import-resolver/src/import_resolver.ts b/packages/kbn-import-resolver/src/import_resolver.ts index 1b41418a5cb24..9ca16981b2afc 100644 --- a/packages/kbn-import-resolver/src/import_resolver.ts +++ b/packages/kbn-import-resolver/src/import_resolver.ts @@ -122,11 +122,6 @@ export class ImportResolver { return true; } - // ignore amd require done by ace syntax plugin - if (req === 'ace/lib/dom') { - return true; - } - // typescript validates these imports fine and they're purely virtual thanks to ambient type definitions in @elastic/eui so /shrug if ( req.startsWith('@elastic/eui/src/components/') || diff --git a/packages/kbn-import-resolver/src/integration_tests/import_resolver.test.ts b/packages/kbn-import-resolver/src/integration_tests/import_resolver.test.ts index f484de7904f06..1089f811b6e98 100644 --- a/packages/kbn-import-resolver/src/integration_tests/import_resolver.test.ts +++ b/packages/kbn-import-resolver/src/integration_tests/import_resolver.test.ts @@ -99,12 +99,6 @@ describe('#resolve()', () => { } `); - expect(resolver.resolve('ace/lib/dom', FIXTURES_DIR)).toMatchInlineSnapshot(` - Object { - "type": "ignore", - } - `); - expect(resolver.resolve('@elastic/eui/src/components/foo', FIXTURES_DIR)) .toMatchInlineSnapshot(` Object { diff --git a/packages/kbn-management/settings/setting_ids/index.ts b/packages/kbn-management/settings/setting_ids/index.ts index 2b8c5de0b71df..e926007f77f25 100644 --- a/packages/kbn-management/settings/setting_ids/index.ts +++ b/packages/kbn-management/settings/setting_ids/index.ts @@ -142,6 +142,7 @@ export const OBSERVABILITY_APM_ENABLE_SERVICE_INVENTORY_TABLE_SEARCH_BAR = 'observability:apmEnableServiceInventoryTableSearchBar'; export const OBSERVABILITY_LOGS_EXPLORER_ALLOWED_DATA_VIEWS_ID = 'observability:logsExplorer:allowedDataViews'; +export const OBSERVABILITY_LOGS_SHARED_NEW_LOGS_OVERVIEW_ID = 'observability:newLogsOverview'; export const OBSERVABILITY_ENTITY_CENTRIC_EXPERIENCE = 'observability:entityCentricExperience'; export const OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID = 'observability:logSources'; export const OBSERVABILITY_ENABLE_LOGS_STREAM = 'observability:enableLogsStream'; diff --git a/packages/kbn-mock-idp-plugin/public/login_page.tsx b/packages/kbn-mock-idp-plugin/public/login_page.tsx index eeb51e3aa37c1..92561ca51f584 100644 --- a/packages/kbn-mock-idp-plugin/public/login_page.tsx +++ b/packages/kbn-mock-idp-plugin/public/login_page.tsx @@ -7,21 +7,24 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import type { EuiComboBoxOptionOption } from '@elastic/eui'; import { EuiButton, - EuiPageTemplate, - EuiEmptyPrompt, + EuiButtonEmpty, EuiComboBox, - EuiInlineEditTitle, + EuiEmptyPrompt, EuiFormRow, + EuiInlineEditTitle, + EuiPageTemplate, EuiSpacer, - EuiComboBoxOptionOption, - EuiButtonEmpty, } from '@elastic/eui'; -import React, { ChangeEvent, useEffect, useState, useRef } from 'react'; -import { FormikProvider, useFormik, Field, Form } from 'formik'; +import { Field, Form, FormikProvider, useFormik } from 'formik'; +import type { ChangeEvent } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; + +import type { CoreStart } from '@kbn/core-lifecycle-browser'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { CoreStart } from '@kbn/core-lifecycle-browser'; + import { useAuthenticator } from './role_switcher'; export const LoginPage = () => { diff --git a/packages/kbn-mock-idp-plugin/public/plugin.tsx b/packages/kbn-mock-idp-plugin/public/plugin.tsx index 0d168a5d6ec32..c1f733027f656 100644 --- a/packages/kbn-mock-idp-plugin/public/plugin.tsx +++ b/packages/kbn-mock-idp-plugin/public/plugin.tsx @@ -7,14 +7,16 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { PluginInitializer } from '@kbn/core-plugins-browser'; import React from 'react'; import ReactDOM from 'react-dom'; -import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; + +import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; +import type { PluginInitializer } from '@kbn/core-plugins-browser'; import { I18nProvider } from '@kbn/i18n-react'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { MOCK_IDP_LOGIN_PATH } from '@kbn/mock-idp-utils/src/constants'; -import type { CloudStart, CloudSetup } from '@kbn/cloud-plugin/public'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; + import { RoleSwitcher } from './role_switcher'; export interface PluginSetupDependencies { diff --git a/packages/kbn-mock-idp-plugin/public/reload_page_toast.tsx b/packages/kbn-mock-idp-plugin/public/reload_page_toast.tsx index 8f21570555626..c3f93f3269da1 100644 --- a/packages/kbn-mock-idp-plugin/public/reload_page_toast.tsx +++ b/packages/kbn-mock-idp-plugin/public/reload_page_toast.tsx @@ -7,13 +7,13 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; -import { toMountPoint } from '@kbn/react-kibana-mount'; -import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import type { ToastInput } from '@kbn/core-notifications-browser'; import type { I18nStart } from '@kbn/core-i18n-browser'; +import type { ToastInput } from '@kbn/core-notifications-browser'; import type { ThemeServiceStart } from '@kbn/core-theme-browser'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import type { AuthenticatedUser } from '@kbn/security-plugin-types-common'; export const DATA_TEST_SUBJ_PAGE_RELOAD_BUTTON = 'pageReloadButton'; diff --git a/packages/kbn-mock-idp-plugin/public/role_switcher.tsx b/packages/kbn-mock-idp-plugin/public/role_switcher.tsx index d0ffa3a67a697..347293abbc6c7 100644 --- a/packages/kbn-mock-idp-plugin/public/role_switcher.tsx +++ b/packages/kbn-mock-idp-plugin/public/role_switcher.tsx @@ -7,13 +7,15 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import { EuiButton, EuiContextMenu, EuiPopover } from '@elastic/eui'; import React, { useEffect, useState } from 'react'; -import { EuiButton, EuiPopover, EuiContextMenu } from '@elastic/eui'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { CoreStart } from '@kbn/core-lifecycle-browser'; import useAsyncFn from 'react-use/lib/useAsyncFn'; -import type { AuthenticatedUser } from '@kbn/security-plugin-types-common'; + +import type { CoreStart } from '@kbn/core-lifecycle-browser'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; import { MOCK_IDP_REALM_NAME, MOCK_IDP_REALM_TYPE } from '@kbn/mock-idp-utils/src/constants'; +import type { AuthenticatedUser } from '@kbn/security-plugin-types-common'; + import { createReloadPageToast } from './reload_page_toast'; import type { CreateSAMLResponseParams } from '../server'; diff --git a/packages/kbn-mock-idp-plugin/server/plugin.ts b/packages/kbn-mock-idp-plugin/server/plugin.ts index 27d5b96a4cbfc..fc9043099b197 100644 --- a/packages/kbn-mock-idp-plugin/server/plugin.ts +++ b/packages/kbn-mock-idp-plugin/server/plugin.ts @@ -7,17 +7,18 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { PluginInitializer, Plugin } from '@kbn/core-plugins-server'; +import { resolve } from 'path'; + +import type { CloudSetup } from '@kbn/cloud-plugin/server'; import { schema } from '@kbn/config-schema'; import type { TypeOf } from '@kbn/config-schema'; -import { MOCK_IDP_LOGIN_PATH, MOCK_IDP_LOGOUT_PATH, createSAMLResponse } from '@kbn/mock-idp-utils'; +import type { Plugin, PluginInitializer } from '@kbn/core-plugins-server'; import { + readRolesFromResource, SERVERLESS_ROLES_ROOT_PATH, STATEFUL_ROLES_ROOT_PATH, - readRolesFromResource, } from '@kbn/es'; -import { resolve } from 'path'; -import { CloudSetup } from '@kbn/cloud-plugin/server'; +import { createSAMLResponse, MOCK_IDP_LOGIN_PATH, MOCK_IDP_LOGOUT_PATH } from '@kbn/mock-idp-utils'; export interface PluginSetupDependencies { cloud: CloudSetup; diff --git a/packages/kbn-mock-idp-utils/src/utils.ts b/packages/kbn-mock-idp-utils/src/utils.ts index 85d37fdce5ab5..af07d69cfe936 100644 --- a/packages/kbn-mock-idp-utils/src/utils.ts +++ b/packages/kbn-mock-idp-utils/src/utils.ts @@ -7,23 +7,23 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { Client } from '@elastic/elasticsearch'; - -import { SignedXml } from 'xml-crypto'; -import { KBN_KEY_PATH, KBN_CERT_PATH } from '@kbn/dev-utils'; -import { readFile } from 'fs/promises'; +import type { Client } from '@elastic/elasticsearch'; import { X509Certificate } from 'crypto'; +import { readFile } from 'fs/promises'; +import { SignedXml } from 'xml-crypto'; + +import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { - MOCK_IDP_REALM_NAME, - MOCK_IDP_ENTITY_ID, - MOCK_IDP_ROLE_MAPPING_NAME, - MOCK_IDP_ATTRIBUTE_PRINCIPAL, - MOCK_IDP_ATTRIBUTE_ROLES, MOCK_IDP_ATTRIBUTE_EMAIL, MOCK_IDP_ATTRIBUTE_NAME, + MOCK_IDP_ATTRIBUTE_PRINCIPAL, + MOCK_IDP_ATTRIBUTE_ROLES, + MOCK_IDP_ENTITY_ID, MOCK_IDP_LOGIN_PATH, MOCK_IDP_LOGOUT_PATH, + MOCK_IDP_REALM_NAME, + MOCK_IDP_ROLE_MAPPING_NAME, } from './constants'; /** diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 539d3098030e0..52a837724480d 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -247,6 +247,18 @@ export function getWebpackConfig( }, }, }, + { + test: /node_modules\/@?xstate5\/.*\.js$/, + use: { + loader: 'babel-loader', + options: { + babelrc: false, + envName: worker.dist ? 'production' : 'development', + presets: [BABEL_PRESET], + plugins: ['@babel/plugin-transform-logical-assignment-operators'], + }, + }, + }, { test: /\.(html|md|txt|tmpl)$/, use: { diff --git a/packages/kbn-storybook/src/webpack.config.ts b/packages/kbn-storybook/src/webpack.config.ts index fb901692e7f66..b03d78dbbc190 100644 --- a/packages/kbn-storybook/src/webpack.config.ts +++ b/packages/kbn-storybook/src/webpack.config.ts @@ -125,6 +125,17 @@ export default ({ config: storybookConfig }: { config: Configuration }) => { }, ], }, + { + test: /node_modules\/@?xstate5\/.*\.js$/, + use: { + loader: 'babel-loader', + options: { + babelrc: false, + presets: [require.resolve('@kbn/babel-preset/webpack_preset')], + plugins: ['@babel/plugin-transform-logical-assignment-operators'], + }, + }, + }, ], }, plugins: [new IgnoreNotFoundExportPlugin()], diff --git a/packages/kbn-test/src/jest/resolver.js b/packages/kbn-test/src/jest/resolver.js index 27e0b14876587..8f985e9463962 100644 --- a/packages/kbn-test/src/jest/resolver.js +++ b/packages/kbn-test/src/jest/resolver.js @@ -70,7 +70,7 @@ module.exports = (request, options) => { return FILE_MOCK; } - if (reqExt === '.worker' && (reqBasename.endsWith('.ace') || reqBasename.endsWith('.editor'))) { + if (reqExt === '.worker' && reqBasename.endsWith('.editor')) { return WORKER_MOCK; } } diff --git a/packages/kbn-ui-shared-deps-npm/BUILD.bazel b/packages/kbn-ui-shared-deps-npm/BUILD.bazel index 48f234b0bfe10..ad3f3474f1b4e 100644 --- a/packages/kbn-ui-shared-deps-npm/BUILD.bazel +++ b/packages/kbn-ui-shared-deps-npm/BUILD.bazel @@ -53,7 +53,6 @@ RUNTIME_DEPS = [ "@npm//jquery", "@npm//lodash", "@npm//moment-timezone", - "@npm//react-ace", "@npm//react-dom", "@npm//react-router-dom", "@npm//react-router-dom-v5-compat", diff --git a/packages/kbn-ui-shared-deps-npm/webpack.config.js b/packages/kbn-ui-shared-deps-npm/webpack.config.js index 3b16430aeb724..926a041a72c3d 100644 --- a/packages/kbn-ui-shared-deps-npm/webpack.config.js +++ b/packages/kbn-ui-shared-deps-npm/webpack.config.js @@ -88,7 +88,6 @@ module.exports = (_, argv) => { 'moment-timezone/moment-timezone', 'moment-timezone/data/packed/latest.json', 'moment', - 'react-ace', 'react-dom', 'react-dom/server', 'react-router-dom', diff --git a/packages/kbn-user-profile-components/src/hooks/use_update_user_profile.test.tsx b/packages/kbn-user-profile-components/src/hooks/use_update_user_profile.test.tsx index 0837292d36ac6..87d41bd222637 100644 --- a/packages/kbn-user-profile-components/src/hooks/use_update_user_profile.test.tsx +++ b/packages/kbn-user-profile-components/src/hooks/use_update_user_profile.test.tsx @@ -7,8 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React from 'react'; import { act, renderHook, type WrapperComponent } from '@testing-library/react-hooks'; +import React from 'react'; import { BehaviorSubject, first, lastValueFrom, of } from 'rxjs'; import { coreMock } from '@kbn/core/public/mocks'; diff --git a/packages/kbn-user-profile-components/src/hooks/use_update_user_profile.tsx b/packages/kbn-user-profile-components/src/hooks/use_update_user_profile.tsx index 8c276dc533f6c..57aeec7a51d5a 100644 --- a/packages/kbn-user-profile-components/src/hooks/use_update_user_profile.tsx +++ b/packages/kbn-user-profile-components/src/hooks/use_update_user_profile.tsx @@ -8,13 +8,14 @@ */ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import React, { useCallback, useRef, useState, useEffect } from 'react'; +import { merge } from 'lodash'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import useObservable from 'react-use/lib/useObservable'; + import { i18n } from '@kbn/i18n'; -import { merge } from 'lodash'; -import type { UserProfileData } from '../types'; import { useUserProfiles } from '../services'; +import type { UserProfileData } from '../types'; interface Props { notificationSuccess?: { @@ -74,23 +75,25 @@ export const useUpdateUserProfile = ({ { title: notificationTitle, text: ( - - -

{pageReloadText}

- window.location.reload()} - data-test-subj="windowReloadButton" - > - {i18n.translate( - 'userProfileComponents.updateUserProfile.notification.requiresPageReloadButtonLabel', - { - defaultMessage: 'Reload page', - } - )} - -
-
+ <> +

{pageReloadText}

+ + + window.location.reload()} + data-test-subj="windowReloadButton" + > + {i18n.translate( + 'userProfileComponents.updateUserProfile.notification.requiresPageReloadButtonLabel', + { + defaultMessage: 'Reload page', + } + )} + + + + ), }, { diff --git a/packages/kbn-user-profile-components/src/services.tsx b/packages/kbn-user-profile-components/src/services.tsx index 726814f370412..7cf7a2d66c82f 100644 --- a/packages/kbn-user-profile-components/src/services.tsx +++ b/packages/kbn-user-profile-components/src/services.tsx @@ -7,12 +7,13 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { FC, PropsWithChildren, useContext } from 'react'; +import type { FC, PropsWithChildren } from 'react'; +import React, { useContext } from 'react'; import type { I18nStart } from '@kbn/core-i18n-browser'; import type { NotificationsStart, ToastOptions } from '@kbn/core-notifications-browser'; import type { ThemeServiceStart } from '@kbn/core-theme-browser'; -import { toMountPoint } from '@kbn/react-kibana-mount'; +import type { toMountPoint } from '@kbn/react-kibana-mount'; import type { UserProfileAPIClient } from './types'; diff --git a/packages/kbn-user-profile-components/src/types.ts b/packages/kbn-user-profile-components/src/types.ts index abdc9fba618ad..ff74061e0ef39 100644 --- a/packages/kbn-user-profile-components/src/types.ts +++ b/packages/kbn-user-profile-components/src/types.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { Observable } from 'rxjs'; +import type { Observable } from 'rxjs'; /** * Avatar stored in user profile. diff --git a/packages/kbn-user-profile-components/src/user_avatar.tsx b/packages/kbn-user-profile-components/src/user_avatar.tsx index 2b55e7909ef8c..5c31d7112afde 100644 --- a/packages/kbn-user-profile-components/src/user_avatar.tsx +++ b/packages/kbn-user-profile-components/src/user_avatar.tsx @@ -11,8 +11,8 @@ import type { EuiAvatarProps } from '@elastic/eui'; import { EuiAvatar, useEuiTheme } from '@elastic/eui'; import type { FunctionComponent } from 'react'; import React from 'react'; -import { UserProfileAvatarData } from './types'; +import type { UserProfileAvatarData } from './types'; import type { UserProfile, UserProfileUserInfo } from './user_profile'; import { getUserAvatarColor, diff --git a/packages/kbn-user-profile-components/src/user_profile.ts b/packages/kbn-user-profile-components/src/user_profile.ts index 082f6d11d7ec0..7c029f606fa7f 100644 --- a/packages/kbn-user-profile-components/src/user_profile.ts +++ b/packages/kbn-user-profile-components/src/user_profile.ts @@ -8,6 +8,7 @@ */ import { VISUALIZATION_COLORS } from '@elastic/eui'; + import type { UserProfileAvatarData, UserProfileData } from './types'; /** diff --git a/packages/kbn-user-profile-components/src/user_profiles_popover.test.tsx b/packages/kbn-user-profile-components/src/user_profiles_popover.test.tsx index 743dbf3232114..69d33763ba82b 100644 --- a/packages/kbn-user-profile-components/src/user_profiles_popover.test.tsx +++ b/packages/kbn-user-profile-components/src/user_profiles_popover.test.tsx @@ -9,8 +9,8 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { UserProfile } from './user_profile'; +import type { UserProfile } from './user_profile'; import { UserProfilesPopover } from './user_profiles_popover'; const userProfiles: UserProfile[] = [ diff --git a/packages/kbn-user-profile-components/src/user_profiles_popover.tsx b/packages/kbn-user-profile-components/src/user_profiles_popover.tsx index 197f6952110dd..decd3607133ab 100644 --- a/packages/kbn-user-profile-components/src/user_profiles_popover.tsx +++ b/packages/kbn-user-profile-components/src/user_profiles_popover.tsx @@ -7,12 +7,13 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { EuiPopoverProps, EuiContextMenuPanelProps } from '@elastic/eui'; +import type { EuiContextMenuPanelProps, EuiPopoverProps } from '@elastic/eui'; +import { EuiContextMenuPanel, EuiPopover, useGeneratedHtmlId } from '@elastic/eui'; import React from 'react'; -import { EuiPopover, EuiContextMenuPanel, useGeneratedHtmlId } from '@elastic/eui'; -import { UserProfilesSelectable, UserProfilesSelectableProps } from './user_profiles_selectable'; import type { UserProfileWithAvatar } from './user_avatar'; +import type { UserProfilesSelectableProps } from './user_profiles_selectable'; +import { UserProfilesSelectable } from './user_profiles_selectable'; /** * Props of {@link UserProfilesPopover} component diff --git a/packages/kbn-user-profile-components/src/user_profiles_selectable.test.tsx b/packages/kbn-user-profile-components/src/user_profiles_selectable.test.tsx index 8ba8fe6e61d07..3bd720e97d96d 100644 --- a/packages/kbn-user-profile-components/src/user_profiles_selectable.test.tsx +++ b/packages/kbn-user-profile-components/src/user_profiles_selectable.test.tsx @@ -7,10 +7,11 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { mountWithIntl as mount } from '@kbn/test-jest-helpers'; import React from 'react'; -import { UserProfile } from './user_profile'; +import { mountWithIntl as mount } from '@kbn/test-jest-helpers'; + +import type { UserProfile } from './user_profile'; import { UserProfilesSelectable } from './user_profiles_selectable'; const userProfiles: UserProfile[] = [ diff --git a/packages/kbn-user-profile-components/src/user_profiles_selectable.tsx b/packages/kbn-user-profile-components/src/user_profiles_selectable.tsx index 8dadde4427739..b6684f6e1fdb9 100644 --- a/packages/kbn-user-profile-components/src/user_profiles_selectable.tsx +++ b/packages/kbn-user-profile-components/src/user_profiles_selectable.tsx @@ -10,15 +10,15 @@ import type { EuiSelectableOption, EuiSelectableProps } from '@elastic/eui'; import { EuiButtonEmpty, + EuiCallOut, EuiFlexGroup, EuiFlexItem, + EuiHighlight, EuiHorizontalRule, EuiPanel, EuiSelectable, EuiSpacer, EuiText, - EuiCallOut, - EuiHighlight, } from '@elastic/eui'; import type { ReactNode } from 'react'; import React, { useEffect, useState } from 'react'; @@ -26,9 +26,9 @@ import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { getUserDisplayLabel, getUserDisplayName } from './user_profile'; import type { UserProfileWithAvatar } from './user_avatar'; import { UserAvatar } from './user_avatar'; +import { getUserDisplayLabel, getUserDisplayName } from './user_profile'; const NULL_OPTION_KEY = 'null'; diff --git a/packages/kbn-user-profile-components/src/user_tooltip.tsx b/packages/kbn-user-profile-components/src/user_tooltip.tsx index 040e497adb497..80739d40802f5 100644 --- a/packages/kbn-user-profile-components/src/user_tooltip.tsx +++ b/packages/kbn-user-profile-components/src/user_tooltip.tsx @@ -7,15 +7,15 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { EuiText, EuiToolTipProps } from '@elastic/eui'; -import { EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import type { EuiToolTipProps } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui'; import type { FunctionComponent } from 'react'; import React from 'react'; -import type { UserProfileUserInfo } from './user_profile'; +import type { UserProfileAvatarData } from './types'; import { UserAvatar } from './user_avatar'; +import type { UserProfileUserInfo } from './user_profile'; import { getUserDisplayName } from './user_profile'; -import { UserProfileAvatarData } from './types'; /** * Props of {@link UserToolTip} component diff --git a/packages/kbn-xstate-utils/kibana.jsonc b/packages/kbn-xstate-utils/kibana.jsonc index cd1151a3f2103..1fb3507854b98 100644 --- a/packages/kbn-xstate-utils/kibana.jsonc +++ b/packages/kbn-xstate-utils/kibana.jsonc @@ -1,5 +1,5 @@ { - "type": "shared-common", + "type": "shared-browser", "id": "@kbn/xstate-utils", "owner": "@elastic/obs-ux-logs-team" } diff --git a/packages/kbn-xstate-utils/src/console_inspector.ts b/packages/kbn-xstate-utils/src/console_inspector.ts new file mode 100644 index 0000000000000..8792ab44f3c28 --- /dev/null +++ b/packages/kbn-xstate-utils/src/console_inspector.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { + ActorRefLike, + AnyActorRef, + InspectedActorEvent, + InspectedEventEvent, + InspectedSnapshotEvent, + InspectionEvent, +} from 'xstate5'; +import { isDevMode } from './dev_tools'; + +export const createConsoleInspector = () => { + if (!isDevMode()) { + return () => {}; + } + + // eslint-disable-next-line no-console + const log = console.info.bind(console); + + const logActorEvent = (actorEvent: InspectedActorEvent) => { + if (isActorRef(actorEvent.actorRef)) { + log( + '✨ %c%s%c is a new actor of type %c%s%c:', + ...styleAsActor(actorEvent.actorRef.id), + ...styleAsKeyword(actorEvent.type), + actorEvent.actorRef + ); + } else { + log('✨ New %c%s%c actor without id:', ...styleAsKeyword(actorEvent.type), actorEvent); + } + }; + + const logEventEvent = (eventEvent: InspectedEventEvent) => { + if (isActorRef(eventEvent.actorRef)) { + log( + '🔔 %c%s%c received event %c%s%c from %c%s%c:', + ...styleAsActor(eventEvent.actorRef.id), + ...styleAsKeyword(eventEvent.event.type), + ...styleAsKeyword(eventEvent.sourceRef?.id), + eventEvent + ); + } else { + log('🔔 Event', ...styleAsKeyword(eventEvent.event.type), ':', eventEvent); + } + }; + + const logSnapshotEvent = (snapshotEvent: InspectedSnapshotEvent) => { + if (isActorRef(snapshotEvent.actorRef)) { + log( + '📸 %c%s%c updated due to %c%s%c:', + ...styleAsActor(snapshotEvent.actorRef.id), + ...styleAsKeyword(snapshotEvent.event.type), + snapshotEvent.snapshot + ); + } else { + log('📸 Snapshot due to %c%s%c:', ...styleAsKeyword(snapshotEvent.event.type), snapshotEvent); + } + }; + + return (inspectionEvent: InspectionEvent) => { + if (inspectionEvent.type === '@xstate.actor') { + logActorEvent(inspectionEvent); + } else if (inspectionEvent.type === '@xstate.event') { + logEventEvent(inspectionEvent); + } else if (inspectionEvent.type === '@xstate.snapshot') { + logSnapshotEvent(inspectionEvent); + } else { + log(`❓ Received inspection event:`, inspectionEvent); + } + }; +}; + +const isActorRef = (actorRefLike: ActorRefLike): actorRefLike is AnyActorRef => + 'id' in actorRefLike; + +const keywordStyle = 'font-weight: bold'; +const styleAsKeyword = (value: any) => [keywordStyle, value, ''] as const; + +const actorStyle = 'font-weight: bold; text-decoration: underline'; +const styleAsActor = (value: any) => [actorStyle, value, ''] as const; diff --git a/packages/kbn-xstate-utils/src/index.ts b/packages/kbn-xstate-utils/src/index.ts index 107585ba2096f..3edf83e8a32c2 100644 --- a/packages/kbn-xstate-utils/src/index.ts +++ b/packages/kbn-xstate-utils/src/index.ts @@ -9,5 +9,6 @@ export * from './actions'; export * from './dev_tools'; +export * from './console_inspector'; export * from './notification_channel'; export * from './types'; diff --git a/src/core/public/styles/_ace_overrides.scss b/src/core/public/styles/_ace_overrides.scss deleted file mode 100644 index ca5230b46acd3..0000000000000 --- a/src/core/public/styles/_ace_overrides.scss +++ /dev/null @@ -1,202 +0,0 @@ -// SASSTODO: Replace with an EUI editor -// Intentionally not using the EuiCodeBlock colors here because they actually change -// hue from light to dark theme. So some colors would change while others wouldn't. -// Seemed weird, so just hexing all the colors but using the `makeHighContrastColor()` -// function to ensure accessible contrast. - -// In order to override the TM (Textmate) theme of Ace/Brace, everywhere, -// it is being scoped by a known outer selector -.kbnBody { - .ace-tm { - $aceBackground: tintOrShade($euiColorLightShade, 50%, 0); - - background-color: $euiColorLightestShade; - color: $euiTextColor; - - .ace_scrollbar { - @include euiScrollBar; - } - - .ace_gutter-active-line, - .ace_marker-layer .ace_active-line { - background-color: transparentize($euiColorLightShade, .3); - } - - .ace_snippet-marker { - width: 100%; - background-color: $aceBackground; - border: none; - } - - .ace_indent-guide { - background: linear-gradient(to left, $euiColorMediumShade 0%, $euiColorMediumShade 1px, transparent 1px, transparent 100%); - } - - .ace_search { - z-index: $euiZLevel1 + 1; - } - - .ace_layer.ace_marker-layer { - overflow: visible; - } - - .ace_warning { - color: $euiColorDanger; - } - - .ace_method { - color: makeHighContrastColor(#DD0A73, $aceBackground); - } - - .ace_url, - .ace_start_triple_quote, - .ace_end_triple_quote { - color: makeHighContrastColor(#00A69B, $aceBackground); - } - - .ace_multi_string { - color: makeHighContrastColor(#009926, $aceBackground); - font-style: italic; - } - - .ace_gutter { - background-color: $euiColorEmptyShade; - color: $euiColorDarkShade; - border-left: 1px solid $aceBackground; - } - - .ace_print-margin { - width: 1px; - background: $euiColorLightShade; - } - - .ace_fold { - background-color: #6B72E6; - } - - .ace_cursor { - color: $euiColorFullShade; - } - - .ace_invisible { - color: $euiColorLightShade; - } - - .ace_storage, - .ace_keyword { - color: makeHighContrastColor(#0079A5, $aceBackground); - } - - .ace_constant { - color: makeHighContrastColor(#900, $aceBackground); - } - - .ace_constant.ace_buildin { - color: makeHighContrastColor(rgb(88, 72, 246), $aceBackground); - } - - .ace_constant.ace_language { - color: makeHighContrastColor(rgb(88, 92, 246), $aceBackground); - } - - .ace_constant.ace_library { - color: makeHighContrastColor(#009926, $aceBackground); - } - - .ace_invalid { - background-color: euiCallOutColor('danger', 'background'); - color: euiCallOutColor('danger', 'foreground'); - } - - .ace_support.ace_function { - color: makeHighContrastColor(rgb(60, 76, 114), $aceBackground); - } - - .ace_support.ace_constant { - color: makeHighContrastColor(#009926, $aceBackground); - } - - .ace_support.ace_type, - .ace_support.ace_class { - color: makeHighContrastColor(rgb(109, 121, 222), $aceBackground); - } - - .ace_keyword.ace_operator { - color: makeHighContrastColor($euiColorDarkShade, $aceBackground); - } - - .ace_string { - color: makeHighContrastColor(#009926, $aceBackground); - } - - .ace_comment { - color: makeHighContrastColor(rgb(76, 136, 107), $aceBackground); - } - - .ace_comment.ace_doc { - color: makeHighContrastColor(#0079A5, $aceBackground); - } - - .ace_comment.ace_doc.ace_tag { - color: makeHighContrastColor($euiColorMediumShade, $aceBackground); - } - - .ace_constant.ace_numeric { - color: makeHighContrastColor(#0079A5, $aceBackground); - } - - .ace_variable { - color: makeHighContrastColor(#0079A5, $aceBackground); - } - - .ace_xml-pe { - color: makeHighContrastColor($euiColorDarkShade, $aceBackground); - } - - .ace_entity.ace_name.ace_function { - color: makeHighContrastColor(#0000A2, $aceBackground); - } - - .ace_heading { - color: makeHighContrastColor(rgb(12, 7, 255), $aceBackground); - } - - .ace_list { - color: makeHighContrastColor(rgb(185, 6, 144), $aceBackground); - } - - .ace_meta.ace_tag { - color: makeHighContrastColor(rgb(0, 22, 142), $aceBackground); - } - - .ace_string.ace_regex { - color: makeHighContrastColor(rgb(255, 0, 0), $aceBackground); - } - - .ace_marker-layer .ace_selection { - background: tintOrShade($euiColorPrimary, 70%, 70%); - } - - &.ace_multiselect .ace_selection.ace_start { - box-shadow: 0 0 3px 0 $euiColorEmptyShade; - } - - .ace_marker-layer .ace_step { - background: tintOrShade($euiColorWarning, 80%, 80%); - } - - .ace_marker-layer .ace_stack { - background: tintOrShade($euiColorSuccess, 80%, 80%); - } - - .ace_marker-layer .ace_bracket { - margin: -1px 0 0 -1px; - border: $euiBorderThin; - } - - .ace_marker-layer .ace_selected-word { - background: $euiColorLightestShade; - border: $euiBorderThin; - } - } -} diff --git a/src/core/public/styles/_index.scss b/src/core/public/styles/_index.scss index 42981c7e07398..cfdb1c7192dcd 100644 --- a/src/core/public/styles/_index.scss +++ b/src/core/public/styles/_index.scss @@ -1,4 +1,3 @@ @import './base'; -@import './ace_overrides'; @import './chrome/index'; @import './rendering/index'; diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index 2eaeb64f8be5f..3572781c4b262 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -87,6 +87,9 @@ export const IGNORE_FILE_GLOBS = [ // Support for including http-client.env.json configurations '**/http-client.env.json', + + // updatecli configuration for driving the UBI/Ironbank image updates + 'updatecli-compose.yaml', ]; /** diff --git a/src/plugins/bfetch/server/ui_settings.ts b/src/plugins/bfetch/server/ui_settings.ts index aee4903d226c0..132dd19ef8b9c 100644 --- a/src/plugins/bfetch/server/ui_settings.ts +++ b/src/plugins/bfetch/server/ui_settings.ts @@ -18,7 +18,7 @@ export function getUiSettings(): Record> { name: i18n.translate('bfetch.disableBfetch', { defaultMessage: 'Disable request batching', }), - value: false, + value: true, description: i18n.translate('bfetch.disableBfetchDesc', { defaultMessage: 'Disables requests batching. This increases number of HTTP requests from Kibana, but allows to debug requests individually.', diff --git a/src/plugins/data/common/search/expressions/esql.ts b/src/plugins/data/common/search/expressions/esql.ts index b6cb039683c9b..966500710fd45 100644 --- a/src/plugins/data/common/search/expressions/esql.ts +++ b/src/plugins/data/common/search/expressions/esql.ts @@ -289,7 +289,7 @@ export const getEsqlFn = ({ getStartDependencies }: EsqlFnArguments) => { }), }) .json(params) - .ok({ json: rawResponse, requestParams }); + .ok({ json: { rawResponse }, requestParams }); }, error(error) { logInspectorRequest() diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/ace/_ui_ace_keyboard_mode.scss b/src/plugins/es_ui_shared/__packages_do_not_import__/ace/_ui_ace_keyboard_mode.scss deleted file mode 100644 index 2ad92f3506b20..0000000000000 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/ace/_ui_ace_keyboard_mode.scss +++ /dev/null @@ -1,24 +0,0 @@ -.kbnUiAceKeyboardHint { - position: absolute; - top: 0; - bottom: 0; - right: 0; - left: 0; - background: transparentize($euiColorEmptyShade, .3); - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - text-align: center; - opacity: 0; - - &:focus { - opacity: 1; - border: 2px solid $euiColorPrimary; - z-index: $euiZLevel1; - } - - &.kbnUiAceKeyboardHint-isInactive { - display: none; - } -} diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/ace/index.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/ace/index.ts deleted file mode 100644 index 6214a2609462c..0000000000000 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/ace/index.ts +++ /dev/null @@ -1,10 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -export { useUIAceKeyboardMode } from './use_ui_ace_keyboard_mode'; diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/ace/use_ui_ace_keyboard_mode.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/ace/use_ui_ace_keyboard_mode.tsx deleted file mode 100644 index f1fe888104783..0000000000000 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/ace/use_ui_ace_keyboard_mode.tsx +++ /dev/null @@ -1,113 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React, { useEffect, useRef } from 'react'; -import * as ReactDOM from 'react-dom'; -import { keys, EuiText } from '@elastic/eui'; -import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; - -import './_ui_ace_keyboard_mode.scss'; -import type { AnalyticsServiceStart, I18nStart, ThemeServiceStart } from '@kbn/core/public'; - -interface StartServices { - analytics: Pick; - i18n: I18nStart; - theme: Pick; -} - -const OverlayText = (startServices: StartServices) => ( - // The point of this element is for accessibility purposes, so ignore eslint error - // in this case - // - - - Press Enter to start editing. - - When you’re done, press Escape to stop editing. - -); - -export function useUIAceKeyboardMode( - aceTextAreaElement: HTMLTextAreaElement | null, - startServices: StartServices, - isAccessibilityOverlayEnabled: boolean = true -) { - const overlayMountNode = useRef(null); - const autoCompleteVisibleRef = useRef(false); - useEffect(() => { - function onDismissOverlay(event: KeyboardEvent) { - if (event.key === keys.ENTER) { - event.preventDefault(); - aceTextAreaElement!.focus(); - } - } - - function enableOverlay() { - if (overlayMountNode.current) { - overlayMountNode.current.focus(); - } - } - - const isAutoCompleteVisible = () => { - const autoCompleter = document.querySelector('.ace_autocomplete'); - if (!autoCompleter) { - return false; - } - // The autoComplete is just hidden when it's closed, not removed from the DOM. - return autoCompleter.style.display !== 'none'; - }; - - const documentKeyDownListener = () => { - autoCompleteVisibleRef.current = isAutoCompleteVisible(); - }; - - const aceKeydownListener = (event: KeyboardEvent) => { - if (event.key === keys.ESCAPE && !autoCompleteVisibleRef.current) { - event.preventDefault(); - event.stopPropagation(); - enableOverlay(); - } - }; - if (aceTextAreaElement && isAccessibilityOverlayEnabled) { - // We don't control HTML elements inside of ace so we imperatively create an element - // that acts as a container and insert it just before ace's textarea element - // so that the overlay lives at the correct spot in the DOM hierarchy. - overlayMountNode.current = document.createElement('div'); - overlayMountNode.current.className = 'kbnUiAceKeyboardHint'; - overlayMountNode.current.setAttribute('role', 'application'); - overlayMountNode.current.tabIndex = 0; - overlayMountNode.current.addEventListener('focus', enableOverlay); - overlayMountNode.current.addEventListener('keydown', onDismissOverlay); - - ReactDOM.render(, overlayMountNode.current); - - aceTextAreaElement.parentElement!.insertBefore(overlayMountNode.current, aceTextAreaElement); - aceTextAreaElement.setAttribute('tabindex', '-1'); - - // Order of events: - // 1. Document capture event fires first and we check whether an autocomplete menu is open on keydown - // (not ideal because this is scoped to the entire document). - // 2. Ace changes it's state (like hiding or showing autocomplete menu) - // 3. We check what button was pressed and whether autocomplete was visible then determine - // whether it should act like a dismiss or if we should display an overlay. - document.addEventListener('keydown', documentKeyDownListener, { capture: true }); - aceTextAreaElement.addEventListener('keydown', aceKeydownListener); - } - return () => { - if (aceTextAreaElement && isAccessibilityOverlayEnabled) { - document.removeEventListener('keydown', documentKeyDownListener, { capture: true }); - aceTextAreaElement.removeEventListener('keydown', aceKeydownListener); - const textAreaContainer = aceTextAreaElement.parentElement; - if (textAreaContainer && textAreaContainer.contains(overlayMountNode.current!)) { - textAreaContainer.removeChild(overlayMountNode.current!); - } - } - }; - }, [aceTextAreaElement, startServices, isAccessibilityOverlayEnabled]); -} diff --git a/src/plugins/es_ui_shared/public/ace/index.ts b/src/plugins/es_ui_shared/public/ace/index.ts deleted file mode 100644 index 9d010117e560e..0000000000000 --- a/src/plugins/es_ui_shared/public/ace/index.ts +++ /dev/null @@ -1,10 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -export { useUIAceKeyboardMode } from '../../__packages_do_not_import__/ace'; diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts index 3b3ccc3fca08f..ddcdb84fa5758 100644 --- a/src/plugins/es_ui_shared/public/index.ts +++ b/src/plugins/es_ui_shared/public/index.ts @@ -12,7 +12,6 @@ * In the future, each top level folder should be exported like that to avoid naming collision */ import * as Forms from './forms'; -import * as ace from './ace'; import * as GlobalFlyout from './global_flyout'; import * as XJson from './xjson'; @@ -47,7 +46,7 @@ export { useAuthorizationContext, } from './authorization'; -export { Forms, ace, GlobalFlyout, XJson }; +export { Forms, GlobalFlyout, XJson }; export { extractQueryParams, attemptToURIDecode } from './url'; diff --git a/src/plugins/es_ui_shared/static/forms/components/index.ts b/src/plugins/es_ui_shared/static/forms/components/index.ts index 4ccfeed19dbfe..2e5dd03390eb7 100644 --- a/src/plugins/es_ui_shared/static/forms/components/index.ts +++ b/src/plugins/es_ui_shared/static/forms/components/index.ts @@ -7,22 +7,6 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -/* -@TODO - -The react-ace and brace/mode/json imports below are loaded eagerly - before this plugin is explicitly loaded by users. This makes -the brace JSON mode, used for JSON syntax highlighting and grammar checking, available across all of Kibana plugins. - -This is not ideal because we are loading JS that is not necessary for Kibana to start, but the alternative -is breaking JSON mode for an unknown number of ace editors across Kibana - not all components reference the underlying -EuiCodeEditor (for instance, explicitly). - -Importing here is a way of preventing a more sophisticated solution to this problem since we want to, eventually, -migrate all code editors over to Monaco. Once that is done, we should remove this import. - */ -import 'react-ace'; -import 'brace/mode/json'; - export * from './field'; export * from './form_row'; export * from './fields'; diff --git a/src/plugins/es_ui_shared/tsconfig.json b/src/plugins/es_ui_shared/tsconfig.json index f3dc3bb39a31d..2747f41b0f370 100644 --- a/src/plugins/es_ui_shared/tsconfig.json +++ b/src/plugins/es_ui_shared/tsconfig.json @@ -24,7 +24,6 @@ "@kbn/storybook", "@kbn/shared-ux-link-redirect-app", "@kbn/code-editor", - "@kbn/react-kibana-context-render", "@kbn/core-application-common", ], "exclude": [ diff --git a/src/plugins/esql/server/ui_settings.ts b/src/plugins/esql/server/ui_settings.ts index 39be07971769b..1ddae41c9b241 100644 --- a/src/plugins/esql/server/ui_settings.ts +++ b/src/plugins/esql/server/ui_settings.ts @@ -21,15 +21,7 @@ export const getUiSettings: () => Record = () => ({ value: true, description: i18n.translate('esql.advancedSettings.enableESQLDescription', { defaultMessage: - 'This setting enables ES|QL in Kibana. By switching it off you will hide the ES|QL user interface from various applications. However, users will be able to access existing ES|QL saved searches, visualizations, etc. If you have feedback on this experience please reach out to us on {link}', - values: { - link: - `` + - i18n.translate('esql.advancedSettings.enableESQL.discussLinkText', { - defaultMessage: 'https://ela.st/esql-feedback', - }) + - '', - }, + 'This setting enables ES|QL in Kibana. By switching it off you will hide the ES|QL user interface from various applications. However, users will be able to access existing ES|QL saved searches, visualizations, etc.', }), requiresPageReload: true, schema: schema.boolean(), 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 dc2d2ad2c5de2..e5ddfbe4dd037 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -705,4 +705,10 @@ export const stackManagementSchema: MakeSchemaFrom = { _meta: { description: 'Non-default value of setting.' }, }, }, + 'observability:newLogsOverview': { + type: 'boolean', + _meta: { + description: 'Enable the new logs overview component.', + }, + }, }; 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 ef20ab223dfb6..2acb487e7ed08 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -56,6 +56,7 @@ export interface UsageStats { 'observability:logsExplorer:allowedDataViews': string[]; 'observability:logSources': string[]; 'observability:enableLogsStream': boolean; + 'observability:newLogsOverview': boolean; 'observability:aiAssistantSimulatedFunctionCalling': boolean; 'observability:aiAssistantSearchConnectorIndexPattern': string; 'visualization:heatmap:maxBuckets': number; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 958280d9eba00..830cffc17cf1c 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -10768,6 +10768,12 @@ "description": "Non-default value of setting." } }, + "observability:newLogsOverview": { + "type": "boolean", + "_meta": { + "description": "Enable the new logs overview component." + } + }, "observability:searchExcludedDataTiers": { "type": "array", "items": { diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.suggestions.test.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.suggestions.test.ts index 28819f7a5c54b..1719adebe7a49 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.suggestions.test.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.suggestions.test.ts @@ -12,6 +12,7 @@ import { DataViewField } from '@kbn/data-views-plugin/common'; import { deepMockedFields, buildDataViewMock } from '@kbn/discover-utils/src/__mocks__'; import { allSuggestionsMock } from '../__mocks__/suggestions'; import { getLensVisMock } from '../__mocks__/lens_vis'; +import { convertDatatableColumnToDataViewFieldSpec } from '@kbn/data-view-utils'; import { UnifiedHistogramSuggestionType } from '../types'; describe('LensVisService suggestions', () => { @@ -198,6 +199,11 @@ describe('LensVisService suggestions', () => { }); test('should return histogramSuggestion if no suggestions returned by the api with the breakdown field if it is given', async () => { + const breakdown = convertDatatableColumnToDataViewFieldSpec({ + name: 'var0', + id: 'var0', + meta: { type: 'number' }, + }); const lensVis = await getLensVisMock({ filters: [], query: { esql: 'from the-data-view | limit 100' }, @@ -207,7 +213,7 @@ describe('LensVisService suggestions', () => { from: '2023-09-03T08:00:00.000Z', to: '2023-09-04T08:56:28.274Z', }, - breakdownField: { name: 'var0' } as DataViewField, + breakdownField: breakdown as DataViewField, columns: [ { id: 'var0', @@ -247,4 +253,54 @@ describe('LensVisService suggestions', () => { expect(lensVis.visContext?.attributes.state.query).toStrictEqual(histogramQuery); }); + + test('should return histogramSuggestion if no suggestions returned by the api with a geo point breakdown field correctly', async () => { + const lensVis = await getLensVisMock({ + filters: [], + query: { esql: 'from the-data-view | limit 100' }, + dataView: dataViewMock, + timeInterval: 'auto', + timeRange: { + from: '2023-09-03T08:00:00.000Z', + to: '2023-09-04T08:56:28.274Z', + }, + breakdownField: { name: 'coordinates' } as DataViewField, + columns: [ + { + id: 'coordinates', + name: 'coordinates', + meta: { + type: 'geo_point', + }, + }, + ], + isPlainRecord: true, + allSuggestions: [], + hasHistogramSuggestionForESQL: true, + }); + + expect(lensVis.currentSuggestionContext?.type).toBe( + UnifiedHistogramSuggestionType.histogramForESQL + ); + expect(lensVis.currentSuggestionContext?.suggestion).toBeDefined(); + expect(lensVis.currentSuggestionContext?.suggestion?.visualizationState).toHaveProperty( + 'layers', + [ + { + layerId: '662552df-2cdc-4539-bf3b-73b9f827252c', + seriesType: 'bar_stacked', + xAccessor: '@timestamp every 30 second', + accessors: ['results'], + layerType: 'data', + }, + ] + ); + + const histogramQuery = { + esql: `from the-data-view | limit 100 +| EVAL timestamp=DATE_TRUNC(30 minute, @timestamp) | stats results = count(*) by timestamp, \`coordinates\` | rename timestamp as \`@timestamp every 30 minute\``, + }; + + expect(lensVis.visContext?.attributes.state.query).toStrictEqual(histogramQuery); + }); }); diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index eccfd663b2557..25bb8be6f6242 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -9,7 +9,11 @@ import { BehaviorSubject, distinctUntilChanged, map, Observable } from 'rxjs'; import { isEqual } from 'lodash'; -import { removeDropCommandsFromESQLQuery, appendToESQLQuery } from '@kbn/esql-utils'; +import { + removeDropCommandsFromESQLQuery, + appendToESQLQuery, + isESQLColumnSortable, +} from '@kbn/esql-utils'; import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; import type { CountIndexPatternColumn, @@ -553,12 +557,17 @@ export class LensVisService { const queryInterval = interval ?? computeInterval(timeRange, this.services.data); const language = getAggregateQueryMode(query); const safeQuery = removeDropCommandsFromESQLQuery(query[language]); - const breakdown = breakdownColumn - ? `, \`${breakdownColumn.name}\` | sort \`${breakdownColumn.name}\` asc` - : ''; + const breakdown = breakdownColumn ? `, \`${breakdownColumn.name}\`` : ''; + + // sort by breakdown column if it's sortable + const sortBy = + breakdownColumn && isESQLColumnSortable(breakdownColumn) + ? ` | sort \`${breakdownColumn.name}\` asc` + : ''; + return appendToESQLQuery( safeQuery, - `| EVAL timestamp=DATE_TRUNC(${queryInterval}, ${dataView.timeFieldName}) | stats results = count(*) by timestamp${breakdown} | rename timestamp as \`${dataView.timeFieldName} every ${queryInterval}\`` + `| EVAL timestamp=DATE_TRUNC(${queryInterval}, ${dataView.timeFieldName}) | stats results = count(*) by timestamp${breakdown}${sortBy} | rename timestamp as \`${dataView.timeFieldName} every ${queryInterval}\`` ); }; diff --git a/src/plugins/vis_default_editor/public/default_editor.tsx b/src/plugins/vis_default_editor/public/default_editor.tsx index f61450c8e85e0..dc9e83e8c3b43 100644 --- a/src/plugins/vis_default_editor/public/default_editor.tsx +++ b/src/plugins/vis_default_editor/public/default_editor.tsx @@ -8,7 +8,6 @@ */ import './index.scss'; -import 'brace/mode/json'; import React, { useEffect, useRef, useState, useCallback } from 'react'; import { EventEmitter } from 'events'; diff --git a/test/functional/apps/management/data_views/_scripted_fields.ts b/test/functional/apps/management/data_views/_scripted_fields.ts index 172537bf4e73a..f86ae72aa5047 100644 --- a/test/functional/apps/management/data_views/_scripted_fields.ts +++ b/test/functional/apps/management/data_views/_scripted_fields.ts @@ -19,10 +19,6 @@ // 3. Filter in Discover by the scripted field // 4. Visualize with aggregation on the scripted field by clicking unifiedFieldList.clickFieldListItemVisualize -// NOTE: Scripted field input is managed by Ace editor, which automatically -// appends closing braces, for exmaple, if you type opening square brace [ -// it will automatically insert a a closing square brace ], etc. - import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; diff --git a/test/functional/apps/management/data_views/_scripted_fields_classic_table.ts b/test/functional/apps/management/data_views/_scripted_fields_classic_table.ts index 063e0b960d52e..4f3d30222e496 100644 --- a/test/functional/apps/management/data_views/_scripted_fields_classic_table.ts +++ b/test/functional/apps/management/data_views/_scripted_fields_classic_table.ts @@ -19,10 +19,6 @@ // 3. Filter in Discover by the scripted field // 4. Visualize with aggregation on the scripted field by clicking unifiedFieldList.clickFieldListItemVisualize -// NOTE: Scripted field input is managed by Ace editor, which automatically -// appends closing braces, for exmaple, if you type opening square brace [ -// it will automatically insert a a closing square brace ], etc. - import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; diff --git a/test/interactive_setup_functional/tests/manual_configuration.ts b/test/interactive_setup_functional/tests/manual_configuration.ts index 8d3956f1cd3c6..59b2391d27f54 100644 --- a/test/interactive_setup_functional/tests/manual_configuration.ts +++ b/test/interactive_setup_functional/tests/manual_configuration.ts @@ -8,6 +8,7 @@ */ import { getUrl, kibanaServerTestUser } from '@kbn/test'; + import type { FtrProviderContext } from '../../functional/ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { diff --git a/test/interactive_setup_functional/tests/manual_configuration_without_security.ts b/test/interactive_setup_functional/tests/manual_configuration_without_security.ts index af95c916622b7..e1fbb205dc53a 100644 --- a/test/interactive_setup_functional/tests/manual_configuration_without_security.ts +++ b/test/interactive_setup_functional/tests/manual_configuration_without_security.ts @@ -8,6 +8,7 @@ */ import { getUrl } from '@kbn/test'; + import type { FtrProviderContext } from '../../functional/ftr_provider_context'; export default function ({ getService, getPageObject }: FtrProviderContext) { diff --git a/test/interactive_setup_functional/tests/manual_configuration_without_tls.ts b/test/interactive_setup_functional/tests/manual_configuration_without_tls.ts index bd5bd3c5f33ab..666cc1cf05290 100644 --- a/test/interactive_setup_functional/tests/manual_configuration_without_tls.ts +++ b/test/interactive_setup_functional/tests/manual_configuration_without_tls.ts @@ -8,6 +8,7 @@ */ import { getUrl, kibanaServerTestUser } from '@kbn/test'; + import type { FtrProviderContext } from '../../functional/ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 0054750a55b24..6c9d805c43b30 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -315,6 +315,8 @@ export default function ({ getService }: PluginFunctionalProviderContext) { // 'xpack.reporting.poll.jobsRefresh.intervalErrorMultiplier (number)', 'xpack.rollup.ui.enabled (boolean?)', 'xpack.saved_object_tagging.cache_refresh_interval (duration?)', + + 'xpack.searchAssistant.ui.enabled (boolean?)', 'xpack.searchInferenceEndpoints.ui.enabled (boolean?)', 'xpack.searchPlayground.ui.enabled (boolean?)', 'xpack.security.loginAssistanceMessage (string?)', diff --git a/tsconfig.base.json b/tsconfig.base.json index 3df30d9cf8c30..188c96734d2ce 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -6,8 +6,6 @@ // START AUTOMATED PACKAGE LISTING "@kbn/aad-fixtures-plugin": ["x-pack/test/alerting_api_integration/common/plugins/aad"], "@kbn/aad-fixtures-plugin/*": ["x-pack/test/alerting_api_integration/common/plugins/aad/*"], - "@kbn/ace": ["packages/kbn-ace"], - "@kbn/ace/*": ["packages/kbn-ace/*"], "@kbn/actions-plugin": ["x-pack/plugins/actions"], "@kbn/actions-plugin/*": ["x-pack/plugins/actions/*"], "@kbn/actions-simulators-plugin": ["x-pack/test/alerting_api_integration/common/plugins/actions_simulators"], @@ -16,6 +14,8 @@ "@kbn/actions-types/*": ["packages/kbn-actions-types/*"], "@kbn/advanced-settings-plugin": ["src/plugins/advanced_settings"], "@kbn/advanced-settings-plugin/*": ["src/plugins/advanced_settings/*"], + "@kbn/ai-assistant": ["x-pack/packages/kbn-ai-assistant"], + "@kbn/ai-assistant/*": ["x-pack/packages/kbn-ai-assistant/*"], "@kbn/ai-assistant-management-plugin": ["src/plugins/ai_assistant_management/selection"], "@kbn/ai-assistant-management-plugin/*": ["src/plugins/ai_assistant_management/selection/*"], "@kbn/aiops-change-point-detection": ["x-pack/packages/ml/aiops_change_point_detection"], @@ -1298,6 +1298,8 @@ "@kbn/observability-get-padded-alert-time-range-util/*": ["x-pack/packages/observability/get_padded_alert_time_range_util/*"], "@kbn/observability-logs-explorer-plugin": ["x-pack/plugins/observability_solution/observability_logs_explorer"], "@kbn/observability-logs-explorer-plugin/*": ["x-pack/plugins/observability_solution/observability_logs_explorer/*"], + "@kbn/observability-logs-overview": ["x-pack/packages/observability/logs_overview"], + "@kbn/observability-logs-overview/*": ["x-pack/packages/observability/logs_overview/*"], "@kbn/observability-onboarding-e2e": ["x-pack/plugins/observability_solution/observability_onboarding/e2e"], "@kbn/observability-onboarding-e2e/*": ["x-pack/plugins/observability_solution/observability_onboarding/e2e/*"], "@kbn/observability-onboarding-plugin": ["x-pack/plugins/observability_solution/observability_onboarding"], diff --git a/updatecli-compose.yaml b/updatecli-compose.yaml new file mode 100644 index 0000000000000..8ad9bd6df8afb --- /dev/null +++ b/updatecli-compose.yaml @@ -0,0 +1,14 @@ +# Config file for `updatecli compose ...`. +# https://www.updatecli.io/docs/core/compose/ +policies: + - name: Handle ironbank bumps + policy: ghcr.io/elastic/oblt-updatecli-policies/ironbank/templates:0.3.0@sha256:b0c841d8fb294e6b58359462afbc83070dca375ac5dd0c5216c8926872a98bb1 + values: + - .github/updatecli/values.d/scm.yml + - .github/updatecli/values.d/ironbank.yml + + - name: Update Updatecli policies + policy: ghcr.io/updatecli/policies/autodiscovery/updatecli:0.4.0@sha256:254367f5b1454fd6032b88b314450cd3b6d5e8d5b6c953eb242a6464105eb869 + values: + - .github/updatecli/values.d/scm.yml + - .github/updatecli/values.d/updatecli-compose.yml \ No newline at end of file diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index a46e291093411..7afbc9dc704c4 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -8,6 +8,7 @@ "packages/ml/aiops_log_rate_analysis", "plugins/aiops" ], + "xpack.aiAssistant": "packages/kbn-ai-assistant", "xpack.alerting": "plugins/alerting", "xpack.eventLog": "plugins/event_log", "xpack.stackAlerts": "plugins/stack_alerts", @@ -44,9 +45,15 @@ "xpack.dataVisualizer": "plugins/data_visualizer", "xpack.exploratoryView": "plugins/observability_solution/exploratory_view", "xpack.fileUpload": "plugins/file_upload", - "xpack.globalSearch": ["plugins/global_search"], - "xpack.globalSearchBar": ["plugins/global_search_bar"], - "xpack.graph": ["plugins/graph"], + "xpack.globalSearch": [ + "plugins/global_search" + ], + "xpack.globalSearchBar": [ + "plugins/global_search_bar" + ], + "xpack.graph": [ + "plugins/graph" + ], "xpack.grokDebugger": "plugins/grokdebugger", "xpack.idxMgmt": "plugins/index_management", "xpack.idxMgmtPackage": "packages/index-management", @@ -68,9 +75,13 @@ "xpack.licenseMgmt": "plugins/license_management", "xpack.licensing": "plugins/licensing", "xpack.lists": "plugins/lists", - "xpack.logstash": ["plugins/logstash"], + "xpack.logstash": [ + "plugins/logstash" + ], "xpack.main": "legacy/plugins/xpack_main", - "xpack.maps": ["plugins/maps"], + "xpack.maps": [ + "plugins/maps" + ], "xpack.metricsData": "plugins/observability_solution/metrics_data_access", "xpack.ml": [ "packages/ml/anomaly_utils", @@ -85,7 +96,9 @@ "packages/ml/ui_actions", "plugins/ml" ], - "xpack.monitoring": ["plugins/monitoring"], + "xpack.monitoring": [ + "plugins/monitoring" + ], "xpack.observability": "plugins/observability_solution/observability", "xpack.observabilityAiAssistant": [ "plugins/observability_solution/observability_ai_assistant", @@ -95,12 +108,22 @@ "xpack.observabilityLogsExplorer": "plugins/observability_solution/observability_logs_explorer", "xpack.observability_onboarding": "plugins/observability_solution/observability_onboarding", "xpack.observabilityShared": "plugins/observability_solution/observability_shared", + "xpack.observabilityLogsOverview": [ + "packages/observability/logs_overview/src/components" + ], "xpack.osquery": ["plugins/osquery"], "xpack.painlessLab": "plugins/painless_lab", - "xpack.profiling": ["plugins/observability_solution/profiling"], + "xpack.profiling": [ + "plugins/observability_solution/profiling" + ], "xpack.remoteClusters": "plugins/remote_clusters", - "xpack.reporting": ["plugins/reporting"], - "xpack.rollupJobs": ["packages/rollup", "plugins/rollup"], + "xpack.reporting": [ + "plugins/reporting" + ], + "xpack.rollupJobs": [ + "packages/rollup", + "plugins/rollup" + ], "xpack.runtimeFields": "plugins/runtime_fields", "xpack.screenshotting": "plugins/screenshotting", "xpack.searchSharedUI": "packages/search/shared_ui", @@ -111,7 +134,10 @@ "xpack.searchInferenceEndpoints": "plugins/search_inference_endpoints", "xpack.searchAssistant": "plugins/search_assistant", "xpack.searchProfiler": "plugins/searchprofiler", - "xpack.security": ["plugins/security", "packages/security"], + "xpack.security": [ + "plugins/security", + "packages/security" + ], "xpack.server": "legacy/server", "xpack.serverless": "plugins/serverless", "xpack.serverlessSearch": "plugins/serverless_search", @@ -123,20 +149,30 @@ "xpack.slo": "plugins/observability_solution/slo", "xpack.snapshotRestore": "plugins/snapshot_restore", "xpack.spaces": "plugins/spaces", - "xpack.savedObjectsTagging": ["plugins/saved_objects_tagging"], + "xpack.savedObjectsTagging": [ + "plugins/saved_objects_tagging" + ], "xpack.taskManager": "legacy/plugins/task_manager", "xpack.threatIntelligence": "plugins/threat_intelligence", "xpack.timelines": "plugins/timelines", "xpack.transform": "plugins/transform", "xpack.triggersActionsUI": "plugins/triggers_actions_ui", "xpack.upgradeAssistant": "plugins/upgrade_assistant", - "xpack.uptime": ["plugins/observability_solution/uptime"], - "xpack.synthetics": ["plugins/observability_solution/synthetics"], - "xpack.ux": ["plugins/observability_solution/ux"], + "xpack.uptime": [ + "plugins/observability_solution/uptime" + ], + "xpack.synthetics": [ + "plugins/observability_solution/synthetics" + ], + "xpack.ux": [ + "plugins/observability_solution/ux" + ], "xpack.urlDrilldown": "plugins/drilldowns/url_drilldown", "xpack.watcher": "plugins/watcher" }, - "exclude": ["examples"], + "exclude": [ + "examples" + ], "translations": [ "@kbn/translations-plugin/translations/zh-CN.json", "@kbn/translations-plugin/translations/ja-JP.json", diff --git a/x-pack/packages/kbn-ai-assistant/README.md b/x-pack/packages/kbn-ai-assistant/README.md new file mode 100644 index 0000000000000..d28f93431baa9 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/README.md @@ -0,0 +1,3 @@ +# @kbn/ai-assistant + +Provides components, types and context to render the AI Assistant in plugins. diff --git a/x-pack/packages/kbn-ai-assistant/index.ts b/x-pack/packages/kbn-ai-assistant/index.ts new file mode 100644 index 0000000000000..cf53082cfa4b0 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export * from './src'; diff --git a/x-pack/packages/kbn-ai-assistant/jest.config.js b/x-pack/packages/kbn-ai-assistant/jest.config.js new file mode 100644 index 0000000000000..37d30bae01fa9 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/jest.config.js @@ -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. + */ + +module.exports = { + coverageDirectory: '/target/kibana-coverage/jest/x-pack/packages/kbn_ai_assistant_src', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/packages/kbn-ai-assistant/src/**/*.{ts,tsx}', + '!/x-pack/packages/kbn-ai-assistant/src/*.test.{ts,tsx}', + ], + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/packages/kbn-ai-assistant'], +}; diff --git a/x-pack/packages/kbn-ai-assistant/kibana.jsonc b/x-pack/packages/kbn-ai-assistant/kibana.jsonc new file mode 100644 index 0000000000000..4cddd90431e39 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "id": "@kbn/ai-assistant", + "owner": "@elastic/search-kibana", + "type": "shared-browser" +} diff --git a/x-pack/packages/kbn-ai-assistant/package.json b/x-pack/packages/kbn-ai-assistant/package.json new file mode 100644 index 0000000000000..159ed64f288fd --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/ai-assistant", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0", + "sideEffects": false +} diff --git a/x-pack/packages/kbn-ai-assistant/setup_tests.ts b/x-pack/packages/kbn-ai-assistant/setup_tests.ts new file mode 100644 index 0000000000000..72e0edd0d07f7 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/setup_tests.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. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import '@testing-library/jest-dom'; diff --git a/x-pack/packages/kbn-ai-assistant/src/assets/elastic_ai_assistant.png b/x-pack/packages/kbn-ai-assistant/src/assets/elastic_ai_assistant.png new file mode 100644 index 0000000000000..af10645579683 Binary files /dev/null and b/x-pack/packages/kbn-ai-assistant/src/assets/elastic_ai_assistant.png differ diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/ask_assistant_button.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/buttons/ask_assistant_button.stories.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/ask_assistant_button.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/buttons/ask_assistant_button.stories.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/ask_assistant_button.tsx b/x-pack/packages/kbn-ai-assistant/src/buttons/ask_assistant_button.tsx similarity index 71% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/ask_assistant_button.tsx rename to x-pack/packages/kbn-ai-assistant/src/buttons/ask_assistant_button.tsx index e13ba34110434..624c3df9a1e84 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/ask_assistant_button.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/buttons/ask_assistant_button.tsx @@ -46,12 +46,13 @@ export function AskAssistantButton({ variant, onClick, }: AskAssistantButtonProps) { - const buttonLabel = i18n.translate( - 'xpack.observabilityAiAssistant.askAssistantButton.buttonLabel', - { - defaultMessage: 'Ask Assistant', - } - ); + const buttonLabel = i18n.translate('xpack.aiAssistant.askAssistantButton.buttonLabel', { + defaultMessage: 'Ask Assistant', + }); + + const aiAssistantLabel = i18n.translate('xpack.aiAssistant.aiAssistantLabel', { + defaultMessage: 'AI Assistant', + }); switch (variant) { case 'basic': @@ -84,23 +85,13 @@ export function AskAssistantButton({ return ( {props.isExpanded - ? i18n.translate('xpack.observabilityAiAssistant.hideExpandConversationButton.hide', { + ? i18n.translate('xpack.aiAssistant.hideExpandConversationButton.hide', { defaultMessage: 'Hide chats', }) - : i18n.translate('xpack.observabilityAiAssistant.hideExpandConversationButton.show', { + : i18n.translate('xpack.aiAssistant.hideExpandConversationButton.show', { defaultMessage: 'Show chats', })} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/new_chat_button.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/buttons/new_chat_button.stories.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/new_chat_button.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/buttons/new_chat_button.stories.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/new_chat_button.tsx b/x-pack/packages/kbn-ai-assistant/src/buttons/new_chat_button.tsx similarity index 92% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/new_chat_button.tsx rename to x-pack/packages/kbn-ai-assistant/src/buttons/new_chat_button.tsx index 75cede6344c59..3e515e87c2197 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/new_chat_button.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/buttons/new_chat_button.tsx @@ -18,7 +18,7 @@ export function NewChatButton( iconType="newChat" {...nextProps} > - {i18n.translate('xpack.observabilityAiAssistant.newChatButton', { + {i18n.translate('xpack.aiAssistant.newChatButton', { defaultMessage: 'New chat', })} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_actions_menu.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_actions_menu.tsx similarity index 65% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_actions_menu.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_actions_menu.tsx index 713a0d2311e3c..ac25fe6c3703a 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_actions_menu.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_actions_menu.tsx @@ -16,10 +16,8 @@ import { EuiToolTip, } from '@elastic/eui'; import { ConnectorSelectorBase } from '@kbn/observability-ai-assistant-plugin/public'; -import { useKibana } from '../../hooks/use_kibana'; -import { getSettingsHref } from '../../utils/get_settings_href'; -import { getSettingsKnowledgeBaseHref } from '../../utils/get_settings_kb_href'; -import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors'; +import type { UseGenAIConnectorsResult } from '../hooks/use_genai_connectors'; +import { useKibana } from '../hooks/use_kibana'; export function ChatActionsMenu({ connectors, @@ -32,14 +30,11 @@ export function ChatActionsMenu({ disabled: boolean; onCopyConversationClick: () => void; }) { - const { - application: { navigateToUrl, navigateToApp }, - http, - } = useKibana().services; + const { application, http } = useKibana().services; const [isOpen, setIsOpen] = useState(false); const handleNavigateToConnectors = () => { - navigateToApp('management', { + application?.navigateToApp('management', { path: '/insightsAndAlerting/triggersActionsConnectors/connectors', }); }; @@ -49,11 +44,17 @@ export function ChatActionsMenu({ }; const handleNavigateToSettings = () => { - navigateToUrl(getSettingsHref(http)); + application?.navigateToUrl( + http!.basePath.prepend(`/app/management/kibana/observabilityAiAssistantManagement`) + ); }; const handleNavigateToSettingsKnowledgeBase = () => { - navigateToUrl(getSettingsKnowledgeBaseHref(http)); + application?.navigateToUrl( + http!.basePath.prepend( + `/app/management/kibana/observabilityAiAssistantManagement?tab=knowledge_base` + ) + ); }; return ( @@ -61,10 +62,9 @@ export function ChatActionsMenu({ isOpen={isOpen} button={ @@ -87,24 +87,21 @@ export function ChatActionsMenu({ panels={[ { id: 0, - title: i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.title', { + title: i18n.translate('xpack.aiAssistant.chatHeader.actions.title', { defaultMessage: 'Actions', }), items: [ { - name: i18n.translate( - 'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase', - { - defaultMessage: 'Manage knowledge base', - } - ), + name: i18n.translate('xpack.aiAssistant.chatHeader.actions.knowledgeBase', { + defaultMessage: 'Manage knowledge base', + }), onClick: () => { toggleActionsMenu(); handleNavigateToSettingsKnowledgeBase(); }, }, { - name: i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.settings', { + name: i18n.translate('xpack.aiAssistant.chatHeader.actions.settings', { defaultMessage: 'AI Assistant Settings', }), onClick: () => { @@ -115,7 +112,7 @@ export function ChatActionsMenu({ { name: (
- {i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.connector', { + {i18n.translate('xpack.aiAssistant.chatHeader.actions.connector', { defaultMessage: 'Connector', })}{' '} @@ -129,12 +126,9 @@ export function ChatActionsMenu({ panel: 1, }, { - name: i18n.translate( - 'xpack.observabilityAiAssistant.chatHeader.actions.copyConversation', - { - defaultMessage: 'Copy conversation', - } - ), + name: i18n.translate('xpack.aiAssistant.chatHeader.actions.copyConversation', { + defaultMessage: 'Copy conversation', + }), disabled: !conversationId, onClick: () => { toggleActionsMenu(); @@ -146,7 +140,7 @@ export function ChatActionsMenu({ { id: 1, width: 256, - title: i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.connector', { + title: i18n.translate('xpack.aiAssistant.chatHeader.actions.connector', { defaultMessage: 'Connector', }), content: ( @@ -159,10 +153,9 @@ export function ChatActionsMenu({ data-test-subj="settingsTabGoToConnectorsButton" onClick={handleNavigateToConnectors} > - {i18n.translate( - 'xpack.observabilityAiAssistant.settingsPage.goToConnectorsButtonLabel', - { defaultMessage: 'Manage connectors' } - )} + {i18n.translate('xpack.aiAssistant.settingsPage.goToConnectorsButtonLabel', { + defaultMessage: 'Manage connectors', + })} ), diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.stories.tsx similarity index 98% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_body.stories.tsx index 4e71ecdfd2c12..182cb046cba70 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.stories.tsx @@ -8,9 +8,9 @@ import { ComponentMeta, ComponentStoryObj } from '@storybook/react'; import React from 'react'; import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public'; -import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories'; +import { buildSystemMessage } from '../utils/builders'; +import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories'; import { ChatBody as Component } from './chat_body'; -import { buildSystemMessage } from '../../utils/builders'; const meta: ComponentMeta = { component: Component, diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.test.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.test.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.test.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_body.test.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.tsx similarity index 93% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_body.tsx index 0bf5a8009b635..c3989f6971fff 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.tsx @@ -31,20 +31,20 @@ import type { AuthenticatedUser } from '@kbn/security-plugin/common'; import { euiThemeVars } from '@kbn/ui-theme'; import { findLastIndex } from 'lodash'; import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { useConversation } from '../../hooks/use_conversation'; -import { useGenAIConnectors } from '../../hooks/use_genai_connectors'; -import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base'; -import { useLicense } from '../../hooks/use_license'; -import { useObservabilityAIAssistantChatService } from '../../hooks/use_observability_ai_assistant_chat_service'; -import { useSimulatedFunctionCalling } from '../../hooks/use_simulated_function_calling'; -import { ASSISTANT_SETUP_TITLE, EMPTY_CONVERSATION_TITLE, UPGRADE_LICENSE_TITLE } from '../../i18n'; -import { PromptEditor } from '../prompt_editor/prompt_editor'; +import type { UseKnowledgeBaseResult } from '../hooks/use_knowledge_base'; +import { ASSISTANT_SETUP_TITLE, EMPTY_CONVERSATION_TITLE, UPGRADE_LICENSE_TITLE } from '../i18n'; +import { useAIAssistantChatService } from '../hooks/use_ai_assistant_chat_service'; +import { useSimulatedFunctionCalling } from '../hooks/use_simulated_function_calling'; +import { useGenAIConnectors } from '../hooks/use_genai_connectors'; +import { useConversation } from '../hooks/use_conversation'; import { FlyoutPositionMode } from './chat_flyout'; import { ChatHeader } from './chat_header'; import { ChatTimeline } from './chat_timeline'; import { IncorrectLicensePanel } from './incorrect_license_panel'; import { SimulatedFunctionCallingCallout } from './simulated_function_calling_callout'; import { WelcomeMessage } from './welcome_message'; +import { useLicense } from '../hooks/use_license'; +import { PromptEditor } from '../prompt_editor/prompt_editor'; const fullHeightClassName = css` height: 100%; @@ -110,7 +110,7 @@ export function ChatBody({ showLinkToConversationsApp, onConversationUpdate, onToggleFlyoutPositionMode, - onClose, + navigateToConversation, }: { connectors: ReturnType; currentUser?: Pick; @@ -122,14 +122,14 @@ export function ChatBody({ showLinkToConversationsApp: boolean; onConversationUpdate: (conversation: { conversation: Conversation['conversation'] }) => void; onToggleFlyoutPositionMode?: (flyoutPositionMode: FlyoutPositionMode) => void; - onClose?: () => void; + navigateToConversation: (conversationId?: string) => void; }) { const license = useLicense(); const hasCorrectLicense = license?.hasAtLeast('enterprise'); const euiTheme = useEuiTheme(); const scrollBarStyles = euiScrollBarStyles(euiTheme); - const chatService = useObservabilityAIAssistantChatService(); + const chatService = useAIAssistantChatService(); const { simulatedFunctionCallingEnabled } = useSimulatedFunctionCalling(); @@ -440,12 +440,12 @@ export function ChatBody({ - {i18n.translate('xpack.observabilityAiAssistant.couldNotFindConversationContent', { + {i18n.translate('xpack.aiAssistant.couldNotFindConversationContent', { defaultMessage: 'Could not find a conversation with id {conversationId}. Make sure the conversation exists and you have access to it.', values: { conversationId: initialConversationId }, @@ -470,12 +470,12 @@ export function ChatBody({ {conversation.error ? ( - {i18n.translate('xpack.observabilityAiAssistant.couldNotFindConversationContent', { + {i18n.translate('xpack.aiAssistant.couldNotFindConversationContent', { defaultMessage: 'Could not find a conversation with id {conversationId}. Make sure the conversation exists and you have access to it.', values: { conversationId: initialConversationId }, @@ -500,7 +500,7 @@ export function ChatBody({ saveTitle(newTitle); }} onToggleFlyoutPositionMode={onToggleFlyoutPositionMode} - onClose={onClose} + navigateToConversation={navigateToConversation} /> diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_consolidated_items.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_consolidated_items.tsx similarity index 89% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_consolidated_items.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_consolidated_items.tsx index f31796b8812d2..5771b1fd297d7 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_consolidated_items.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_consolidated_items.tsx @@ -90,11 +90,11 @@ export function ChatConsolidatedItems({ > {!expanded - ? i18n.translate('xpack.observabilityAiAssistant.chatCollapsedItems.showEvents', { + ? i18n.translate('xpack.aiAssistant.chatCollapsedItems.showEvents', { defaultMessage: 'Show {count} events', values: { count: consolidatedItem.length }, }) - : i18n.translate('xpack.observabilityAiAssistant.chatCollapsedItems.hideEvents', { + : i18n.translate('xpack.aiAssistant.chatCollapsedItems.hideEvents', { defaultMessage: 'Hide {count} events', values: { count: consolidatedItem.length }, })} @@ -104,12 +104,9 @@ export function ChatConsolidatedItems({ username="" actions={ {}, + navigateToConversation: () => {}, }; export const ChatFlyout = Template.bind({}); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_flyout.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_flyout.tsx similarity index 88% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_flyout.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_flyout.tsx index 67ac37a88d724..8d636374ac768 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_flyout.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_flyout.tsx @@ -19,16 +19,16 @@ import { i18n } from '@kbn/i18n'; import { Message } from '@kbn/observability-ai-assistant-plugin/common'; import React, { useState } from 'react'; import ReactDOM from 'react-dom'; -import { useConversationKey } from '../../hooks/use_conversation_key'; -import { useConversationList } from '../../hooks/use_conversation_list'; -import { useCurrentUser } from '../../hooks/use_current_user'; -import { useGenAIConnectors } from '../../hooks/use_genai_connectors'; -import { useKibana } from '../../hooks/use_kibana'; -import { useKnowledgeBase } from '../../hooks/use_knowledge_base'; -import { NewChatButton } from '../buttons/new_chat_button'; +import { useConversationKey } from '../hooks/use_conversation_key'; +import { useConversationList } from '../hooks/use_conversation_list'; +import { useCurrentUser } from '../hooks/use_current_user'; +import { useGenAIConnectors } from '../hooks/use_genai_connectors'; import { ChatBody } from './chat_body'; import { ChatInlineEditingContent } from './chat_inline_edit'; import { ConversationList } from './conversation_list'; +import { useKibana } from '../hooks/use_kibana'; +import { useKnowledgeBase } from '../hooks/use_knowledge_base'; +import { NewChatButton } from '../buttons/new_chat_button'; const CONVERSATIONS_SIDEBAR_WIDTH = 260; const CONVERSATIONS_SIDEBAR_WIDTH_COLLAPSED = 34; @@ -46,12 +46,14 @@ export function ChatFlyout({ initialFlyoutPositionMode, isOpen, onClose, + navigateToConversation, }: { initialTitle: string; initialMessages: Message[]; initialFlyoutPositionMode?: FlyoutPositionMode; isOpen: boolean; onClose: () => void; + navigateToConversation(conversationId?: string): void; }) { const { euiTheme } = useEuiTheme(); const breakpoint = useCurrentEuiBreakpoint(); @@ -75,11 +77,7 @@ export function ChatFlyout({ const { services: { - plugins: { - start: { - observabilityAIAssistant: { ObservabilityAIAssistantMultipaneFlyoutContext }, - }, - }, + observabilityAIAssistant: { ObservabilityAIAssistantMultipaneFlyoutContext }, }, } = useKibana(); const conversationList = useConversationList(); @@ -148,8 +146,8 @@ export function ChatFlyout({ > { + if (onClose) onClose(); + navigateToConversation(newConversationId); + }} /> diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_header.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_header.stories.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_header.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_header.stories.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_header.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_header.tsx similarity index 81% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_header.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_header.tsx index c67596fbafd5e..c9f0588a1c90f 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_header.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_header.tsx @@ -21,8 +21,7 @@ import { i18n } from '@kbn/i18n'; import { css } from '@emotion/css'; import { AssistantAvatar } from '@kbn/observability-ai-assistant-plugin/public'; import { ChatActionsMenu } from './chat_actions_menu'; -import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router'; -import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors'; +import type { UseGenAIConnectorsResult } from '../hooks/use_genai_connectors'; import { FlyoutPositionMode } from './chat_flyout'; // needed to prevent InlineTextEdit component from expanding container @@ -50,7 +49,7 @@ export function ChatHeader({ onCopyConversation, onSaveTitle, onToggleFlyoutPositionMode, - onClose, + navigateToConversation, }: { connectors: UseGenAIConnectorsResult; conversationId?: string; @@ -61,36 +60,17 @@ export function ChatHeader({ onCopyConversation: () => void; onSaveTitle: (title: string) => void; onToggleFlyoutPositionMode?: (newFlyoutPositionMode: FlyoutPositionMode) => void; - onClose?: () => void; + navigateToConversation: (nextConversationId?: string) => void; }) { const theme = useEuiTheme(); const breakpoint = useCurrentEuiBreakpoint(); - const router = useObservabilityAIAssistantRouter(); - const [newTitle, setNewTitle] = useState(title); useEffect(() => { setNewTitle(title); }, [title]); - const handleNavigateToConversations = () => { - if (onClose) { - onClose(); - } - - if (conversationId) { - router.push('/conversations/{conversationId}', { - path: { - conversationId, - }, - query: {}, - }); - } else { - router.push('/conversations/new', { path: {}, query: {} }); - } - }; - const handleToggleFlyoutPositionMode = () => { if (flyoutPositionMode) { onToggleFlyoutPositionMode?.( @@ -126,10 +106,9 @@ export function ChatHeader({ className={css` color: ${!!title ? theme.euiTheme.colors.text : theme.euiTheme.colors.subduedText}; `} - inputAriaLabel={i18n.translate( - 'xpack.observabilityAiAssistant.chatHeader.editConversationInput', - { defaultMessage: 'Edit conversation' } - )} + inputAriaLabel={i18n.translate('xpack.aiAssistant.chatHeader.editConversationInput', { + defaultMessage: 'Edit conversation', + })} isReadOnly={ !conversationId || !connectors.selectedConnector || @@ -162,11 +141,11 @@ export function ChatHeader({ content={ flyoutPositionMode === 'overlay' ? i18n.translate( - 'xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock', + 'xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock', { defaultMessage: 'Dock chat' } ) : i18n.translate( - 'xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock', + 'xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock', { defaultMessage: 'Undock chat' } ) } @@ -174,7 +153,7 @@ export function ChatHeader({ > navigateToConversation(conversationId)} /> } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_inline_edit.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_inline_edit.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_inline_edit.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_inline_edit.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item.tsx similarity index 98% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_item.tsx index a1f5d5eb88d2d..23bdbdaea3593 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item.tsx @@ -22,11 +22,11 @@ import { Feedback, TelemetryEventTypeWithPayload, } from '@kbn/observability-ai-assistant-plugin/public'; +import { getRoleTranslation } from '../utils/get_role_translation'; import { ChatItemActions } from './chat_item_actions'; import { ChatItemAvatar } from './chat_item_avatar'; import { ChatItemContentInlinePromptEditor } from './chat_item_content_inline_prompt_editor'; import { ChatTimelineItem } from './chat_timeline'; -import { getRoleTranslation } from '../../utils/get_role_translation'; export interface ChatItemProps extends Omit { onActionClick: ChatActionClickHandler; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_actions.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_actions.tsx similarity index 73% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_actions.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_item_actions.tsx index 4995b0163b7be..cb1196baa6bc1 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_actions.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_actions.tsx @@ -46,12 +46,9 @@ export function ChatItemActions({ <> {canEdit ? ( setIsPopoverOpen(undefined)} > - {i18n.translate( - 'xpack.observabilityAiAssistant.chatTimeline.actions.copyMessageSuccessful', - { - defaultMessage: 'Copied message', - } - )} + {i18n.translate('xpack.aiAssistant.chatTimeline.actions.copyMessageSuccessful', { + defaultMessage: 'Copied message', + })} ) : null} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_avatar.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_avatar.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_avatar.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_item_avatar.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_content_inline_prompt_editor.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_content_inline_prompt_editor.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_content_inline_prompt_editor.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_item_content_inline_prompt_editor.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_title.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_title.tsx similarity index 71% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_title.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_item_title.tsx index 2749ef3635f40..d6a26b0287e46 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_title.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_title.tsx @@ -7,6 +7,7 @@ import { euiThemeVars } from '@kbn/ui-theme'; import React, { ReactNode } from 'react'; +import { css } from '@emotion/react'; interface ChatItemTitleProps { actionsTrigger?: ReactNode; @@ -14,14 +15,15 @@ interface ChatItemTitleProps { } export function ChatItemTitle({ actionsTrigger, title }: ChatItemTitleProps) { + const containerCSS = css` + position: absolute; + top: 2; + right: ${euiThemeVars.euiSizeS}; + `; return ( <> {title} - {actionsTrigger ? ( -
- {actionsTrigger} -
- ) : null} + {actionsTrigger ?
{actionsTrigger}
: null} ); } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_timeline.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.stories.tsx similarity index 98% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_timeline.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.stories.tsx index 88354f41ba293..0afb0c7e79fc0 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_timeline.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.stories.tsx @@ -18,7 +18,7 @@ import { buildFunctionResponseMessage, buildSystemMessage, buildUserMessage, -} from '../../utils/builders'; +} from '../utils/builders'; import { ChatTimeline as Component, type ChatTimelineProps } from './chat_timeline'; export default { @@ -86,11 +86,11 @@ const defaultProps: ComponentProps = { Mathematical Functions: In mathematics, a function maps input values to corresponding output values based on a specific rule or expression. The general process of how a mathematical function works can be summarized as follows: Step 1: Input - You provide an input value to the function, denoted as 'x' in the notation f(x). This value represents the independent variable. - + Step 2: Processing - The function takes the input value and applies a specific rule or algorithm to it. This rule is defined by the function itself and varies depending on the function's expression. - + Step 3: Output - After processing the input, the function produces an output value, denoted as 'f(x)' or 'y'. This output represents the dependent variable and is the result of applying the function's rule to the input. - + Step 4: Uniqueness - A well-defined mathematical function ensures that each input value corresponds to exactly one output value. In other words, the function should yield the same output for the same input whenever it is called.`, }, }), diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_timeline.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.tsx similarity index 96% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_timeline.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.tsx index ec2cf2ca68e7c..9b349f49f3904 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_timeline.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.tsx @@ -18,10 +18,10 @@ import { type ObservabilityAIAssistantChatService, type TelemetryEventTypeWithPayload, } from '@kbn/observability-ai-assistant-plugin/public'; -import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base'; +import type { UseKnowledgeBaseResult } from '../hooks/use_knowledge_base'; import { ChatItem } from './chat_item'; import { ChatConsolidatedItems } from './chat_consolidated_items'; -import { getTimelineItemsfromConversation } from '../../utils/get_timeline_items_from_conversation'; +import { getTimelineItemsfromConversation } from '../utils/get_timeline_items_from_conversation'; export interface ChatTimelineItem extends Pick { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.stories.tsx similarity index 93% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.stories.tsx index b0f72e80c5721..7405b477647fd 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.stories.tsx @@ -7,8 +7,8 @@ import { ComponentMeta, ComponentStoryObj } from '@storybook/react'; import React from 'react'; -import { buildConversation } from '../../utils/builders'; -import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories'; +import { buildConversation } from '../utils/builders'; +import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories'; import { ConversationList as Component } from './conversation_list'; type ConversationListProps = React.ComponentProps; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.tsx similarity index 80% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.tsx index 1b26922bcaf69..e4a7022edc763 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.tsx @@ -21,10 +21,9 @@ import { import { css } from '@emotion/css'; import { i18n } from '@kbn/i18n'; import React, { MouseEvent } from 'react'; -import { useConfirmModal } from '../../hooks/use_confirm_modal'; -import type { UseConversationListResult } from '../../hooks/use_conversation_list'; -import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router'; -import { EMPTY_CONVERSATION_TITLE } from '../../i18n'; +import { useConfirmModal } from '../hooks/use_confirm_modal'; +import type { UseConversationListResult } from '../hooks/use_conversation_list'; +import { EMPTY_CONVERSATION_TITLE } from '../i18n'; import { NewChatButton } from '../buttons/new_chat_button'; const titleClassName = css` @@ -51,15 +50,17 @@ export function ConversationList({ selectedConversationId, onConversationSelect, onConversationDeleteClick, + newConversationHref, + getConversationHref, }: { conversations: UseConversationListResult['conversations']; isLoading: boolean; selectedConversationId?: string; onConversationSelect?: (conversationId?: string) => void; onConversationDeleteClick: (conversationId: string) => void; + newConversationHref?: string; + getConversationHref?: (conversationId: string) => string; }) { - const router = useObservabilityAIAssistantRouter(); - const euiTheme = useEuiTheme(); const scrollBarStyles = euiScrollBarStyles(euiTheme); @@ -70,21 +71,15 @@ export function ConversationList({ `; const { element: confirmDeleteElement, confirm: confirmDeleteCallback } = useConfirmModal({ - title: i18n.translate('xpack.observabilityAiAssistant.flyout.confirmDeleteConversationTitle', { + title: i18n.translate('xpack.aiAssistant.flyout.confirmDeleteConversationTitle', { defaultMessage: 'Delete this conversation?', }), - children: i18n.translate( - 'xpack.observabilityAiAssistant.flyout.confirmDeleteConversationContent', - { - defaultMessage: 'This action cannot be undone.', - } - ), - confirmButtonText: i18n.translate( - 'xpack.observabilityAiAssistant.flyout.confirmDeleteButtonText', - { - defaultMessage: 'Delete conversation', - } - ), + children: i18n.translate('xpack.aiAssistant.flyout.confirmDeleteConversationContent', { + defaultMessage: 'This action cannot be undone.', + }), + confirmButtonText: i18n.translate('xpack.aiAssistant.flyout.confirmDeleteButtonText', { + defaultMessage: 'Delete conversation', + }), }); const displayedConversations = [ @@ -94,7 +89,7 @@ export function ConversationList({ id: '', label: EMPTY_CONVERSATION_TITLE, lastUpdated: '', - href: router.link('/conversations/new'), + href: newConversationHref, }, ] : []), @@ -102,11 +97,7 @@ export function ConversationList({ id: conversation.id, label: conversation.title, lastUpdated: conversation.last_updated, - href: router.link('/conversations/{conversationId}', { - path: { - conversationId: conversation.id, - }, - }), + href: getConversationHref ? getConversationHref(conversation.id) : undefined, })), ]; @@ -123,7 +114,7 @@ export function ConversationList({ - {i18n.translate('xpack.observabilityAiAssistant.conversationList.title', { + {i18n.translate('xpack.aiAssistant.conversationList.title', { defaultMessage: 'Previously', })} @@ -147,12 +138,9 @@ export function ConversationList({ - {i18n.translate( - 'xpack.observabilityAiAssistant.conversationList.errorMessage', - { - defaultMessage: 'Failed to load', - } - )} + {i18n.translate('xpack.aiAssistant.conversationList.errorMessage', { + defaultMessage: 'Failed to load', + })} @@ -185,7 +173,7 @@ export function ConversationList({ ? { iconType: 'trash', 'aria-label': i18n.translate( - 'xpack.observabilityAiAssistant.conversationList.deleteConversationIconLabel', + 'xpack.aiAssistant.conversationList.deleteConversationIconLabel', { defaultMessage: 'Delete', } @@ -211,12 +199,9 @@ export function ConversationList({ {!isLoading && !conversations.error && !displayedConversations?.length ? ( - {i18n.translate( - 'xpack.observabilityAiAssistant.conversationList.noConversations', - { - defaultMessage: 'No conversations', - } - )} + {i18n.translate('xpack.aiAssistant.conversationList.noConversations', { + defaultMessage: 'No conversations', + })} ) : null} @@ -228,7 +213,7 @@ export function ConversationList({ | MouseEvent ) => { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/disclaimer.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/disclaimer.tsx similarity index 91% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/disclaimer.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/disclaimer.tsx index e4eb5176469de..8f9c3abca0e71 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/disclaimer.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/disclaimer.tsx @@ -17,7 +17,7 @@ export function Disclaimer() { textAlign="center" data-test-subj="observabilityAiAssistantDisclaimer" > - {i18n.translate('xpack.observabilityAiAssistant.disclaimer.disclaimerLabel', { + {i18n.translate('xpack.aiAssistant.disclaimer.disclaimerLabel', { defaultMessage: "This chat is powered by an integration with your LLM provider. LLMs are known to sometimes present incorrect information as if it's correct. Elastic supports configuration and connection to the LLM provider and your knowledge base, but is not responsible for the LLM's responses.", })} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/function_list_popover.stories.tsx similarity index 91% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/function_list_popover.stories.tsx index a8f1e23b8173d..62da0b2d14ff8 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/function_list_popover.stories.tsx @@ -7,7 +7,7 @@ import { ComponentStory } from '@storybook/react'; import React from 'react'; -import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories'; +import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories'; import { FunctionListPopover as Component } from './function_list_popover'; export default { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/function_list_popover.tsx similarity index 85% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/function_list_popover.tsx index 16df72f48c91b..d24aae12fd8c6 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/function_list_popover.tsx @@ -22,7 +22,7 @@ import type { EuiSelectableOptionCheckedType } from '@elastic/eui/src/components import { i18n } from '@kbn/i18n'; import { FunctionVisibility } from '@kbn/observability-ai-assistant-plugin/public'; import type { FunctionDefinition } from '@kbn/observability-ai-assistant-plugin/common'; -import { useObservabilityAIAssistantChatService } from '../../hooks/use_observability_ai_assistant_chat_service'; +import { useAIAssistantChatService } from '../hooks/use_ai_assistant_chat_service'; interface FunctionListOption { label: string; @@ -40,7 +40,7 @@ export function FunctionListPopover({ onSelectFunction: (func: string | undefined) => void; disabled: boolean; }) { - const { getFunctions } = useObservabilityAIAssistantChatService(); + const { getFunctions } = useAIAssistantChatService(); const functions = getFunctions(); const [functionOptions, setFunctionOptions] = useState< @@ -80,21 +80,18 @@ export function FunctionListPopover({ content={ mode === 'prompt' ? i18n.translate( - 'xpack.observabilityAiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel', + 'xpack.aiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel', { defaultMessage: 'Select a function' } ) - : i18n.translate( - 'xpack.observabilityAiAssistant.functionListPopover.euiToolTip.clearFunction', - { - defaultMessage: 'Clear function', - } - ) + : i18n.translate('xpack.aiAssistant.functionListPopover.euiToolTip.clearFunction', { + defaultMessage: 'Clear function', + }) } display="block" > - +

{UPGRADE_LICENSE_TITLE}

- {i18n.translate('xpack.observabilityAiAssistant.incorrectLicense.body', { + {i18n.translate('xpack.aiAssistant.incorrectLicense.body', { defaultMessage: 'You need an Enterprise license to use the Elastic AI Assistant.', })} @@ -57,12 +57,9 @@ export function IncorrectLicensePanel() { href="https://www.elastic.co/subscriptions" target="_blank" > - {i18n.translate( - 'xpack.observabilityAiAssistant.incorrectLicense.subscriptionPlansButton', - { - defaultMessage: 'Subscription plans', - } - )} + {i18n.translate('xpack.aiAssistant.incorrectLicense.subscriptionPlansButton', { + defaultMessage: 'Subscription plans', + })}
@@ -70,7 +67,7 @@ export function IncorrectLicensePanel() { data-test-subj="observabilityAiAssistantIncorrectLicensePanelManageLicenseButton" onClick={handleNavigateToLicenseManagement} > - {i18n.translate('xpack.observabilityAiAssistant.incorrectLicense.manageLicense', { + {i18n.translate('xpack.aiAssistant.incorrectLicense.manageLicense', { defaultMessage: 'Manage license', })} diff --git a/x-pack/packages/kbn-ai-assistant/src/chat/index.ts b/x-pack/packages/kbn-ai-assistant/src/chat/index.ts new file mode 100644 index 0000000000000..4b04d7dec81c1 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/chat/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './chat_body'; +export * from './chat_inline_edit'; +export * from './conversation_list'; +export * from './chat_flyout'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/knowledge_base_callout.stories.tsx similarity index 95% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/knowledge_base_callout.stories.tsx index d66729dc75a3d..e87aa161d80c3 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/knowledge_base_callout.stories.tsx @@ -7,7 +7,7 @@ import { ComponentMeta, ComponentStoryObj } from '@storybook/react'; import { merge } from 'lodash'; -import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories'; +import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories'; import { KnowledgeBaseCallout as Component } from './knowledge_base_callout'; const meta: ComponentMeta = { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/knowledge_base_callout.tsx similarity index 85% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/knowledge_base_callout.tsx index 36d6842286aa8..abb296713b2d2 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/knowledge_base_callout.tsx @@ -17,7 +17,7 @@ import { EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base'; +import { UseKnowledgeBaseResult } from '../hooks/use_knowledge_base'; export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnowledgeBaseResult }) { let content: React.ReactNode; @@ -32,7 +32,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow - {i18n.translate('xpack.observabilityAiAssistant.checkingKbAvailability', { + {i18n.translate('xpack.aiAssistant.checkingKbAvailability', { defaultMessage: 'Checking availability of knowledge base', })} @@ -43,7 +43,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow color = 'danger'; content = ( - {i18n.translate('xpack.observabilityAiAssistant.failedToGetStatus', { + {i18n.translate('xpack.aiAssistant.failedToGetStatus', { defaultMessage: 'Failed to get model status.', })} @@ -53,7 +53,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow content = ( {' '} - {i18n.translate('xpack.observabilityAiAssistant.poweredByModel', { + {i18n.translate('xpack.aiAssistant.poweredByModel', { defaultMessage: 'Powered by {model}', values: { model: 'ELSER', @@ -70,7 +70,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow - {i18n.translate('xpack.observabilityAiAssistant.installingKb', { + {i18n.translate('xpack.aiAssistant.installingKb', { defaultMessage: 'Setting up the knowledge base', })} @@ -81,7 +81,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow color = 'danger'; content = ( - {i18n.translate('xpack.observabilityAiAssistant.failedToSetupKnowledgeBase', { + {i18n.translate('xpack.aiAssistant.failedToSetupKnowledgeBase', { defaultMessage: 'Failed to set up knowledge base.', })} @@ -96,7 +96,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow > {' '} - {i18n.translate('xpack.observabilityAiAssistant.setupKb', { + {i18n.translate('xpack.aiAssistant.setupKb', { defaultMessage: 'Improve your experience by setting up the knowledge base.', })} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/simulated_function_calling_callout.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/simulated_function_calling_callout.tsx similarity index 90% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/simulated_function_calling_callout.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/simulated_function_calling_callout.tsx index 41b14e683dd64..26eb589b25dfc 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/simulated_function_calling_callout.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/simulated_function_calling_callout.tsx @@ -17,7 +17,7 @@ export function SimulatedFunctionCallingCallout() { - {i18n.translate('xpack.observabilityAiAssistant.simulatedFunctionCallingCalloutLabel', { + {i18n.translate('xpack.aiAssistant.simulatedFunctionCallingCalloutLabel', { defaultMessage: 'Simulated function calling is enabled. You might see degradated performance.', })} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/starter_prompts.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/starter_prompts.tsx similarity index 85% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/starter_prompts.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/starter_prompts.tsx index 1f5402978d41d..faaecc0024135 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/starter_prompts.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/starter_prompts.tsx @@ -16,9 +16,9 @@ import { } from '@elastic/eui'; import { css } from '@emotion/css'; import { uniq } from 'lodash'; -import { useObservabilityAIAssistantAppService } from '../../hooks/use_observability_ai_assistant_app_service'; -import { useGenAIConnectors } from '../../hooks/use_genai_connectors'; -import { nonNullable } from '../../utils/non_nullable'; +import { useAIAssistantAppService } from '../hooks/use_ai_assistant_app_service'; +import { useGenAIConnectors } from '../hooks/use_genai_connectors'; +import { nonNullable } from '../utils/non_nullable'; const starterPromptClassName = css` max-width: 50%; @@ -30,7 +30,7 @@ const starterPromptInnerClassName = css` `; export function StarterPrompts({ onSelectPrompt }: { onSelectPrompt: (prompt: string) => void }) { - const service = useObservabilityAIAssistantAppService(); + const service = useAIAssistantAppService(); const { connectors } = useGenAIConnectors(); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/welcome_message.tsx similarity index 83% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/welcome_message.tsx index 18f4c5598c6fd..a449235ba44e6 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/welcome_message.tsx @@ -5,19 +5,19 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { css } from '@emotion/css'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, useCurrentEuiBreakpoint } from '@elastic/eui'; import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public'; import { GenerativeAIForObservabilityConnectorFeatureId } from '@kbn/actions-plugin/common'; import { isSupportedConnectorType } from '@kbn/observability-ai-assistant-plugin/public'; -import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base'; -import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors'; +import type { UseKnowledgeBaseResult } from '../hooks/use_knowledge_base'; +import type { UseGenAIConnectorsResult } from '../hooks/use_genai_connectors'; import { Disclaimer } from './disclaimer'; import { WelcomeMessageConnectors } from './welcome_message_connectors'; import { WelcomeMessageKnowledgeBase } from './welcome_message_knowledge_base'; -import { useKibana } from '../../hooks/use_kibana'; import { StarterPrompts } from './starter_prompts'; +import { useKibana } from '../hooks/use_kibana'; const fullHeightClassName = css` height: 100%; @@ -39,22 +39,15 @@ export function WelcomeMessage({ }) { const breakpoint = useCurrentEuiBreakpoint(); - const { - application: { navigateToApp, capabilities }, - plugins: { - start: { - triggersActionsUi: { getAddConnectorFlyout: ConnectorFlyout }, - }, - }, - } = useKibana().services; + const { application, triggersActionsUi } = useKibana().services; const [connectorFlyoutOpen, setConnectorFlyoutOpen] = useState(false); const handleConnectorClick = () => { - if (capabilities.management?.insightsAndAlerting?.triggersActions) { + if (application?.capabilities.management?.insightsAndAlerting?.triggersActions) { setConnectorFlyoutOpen(true); } else { - navigateToApp('management', { + application?.navigateToApp('management', { path: '/insightsAndAlerting/triggersActionsConnectors/connectors', }); } @@ -72,6 +65,11 @@ export function WelcomeMessage({ } }; + const ConnectorFlyout = useMemo( + () => triggersActionsUi.getAddConnectorFlyout, + [triggersActionsUi] + ); + return ( <> {isForbiddenError ? i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel', + 'xpack.aiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel', { defaultMessage: 'Required privileges to get connectors are missing' } ) : i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel', + 'xpack.aiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel', { defaultMessage: 'Could not load connectors' } )} @@ -72,21 +72,15 @@ export function WelcomeMessageConnectors({ return !connectors.loading && connectors.connectors?.length === 0 && onSetupConnectorClick ? (
- {i18n.translate( - 'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2', - { - defaultMessage: - 'Start working with the Elastic AI Assistant by setting up a connector for your AI provider. The model needs to support function calls. When using OpenAI or Azure, we recommend using GPT4.', - } - )} + {i18n.translate('xpack.aiAssistant.initialSetupPanel.setupConnector.description2', { + defaultMessage: + 'Start working with the Elastic AI Assistant by setting up a connector for your AI provider. The model needs to support function calls. When using OpenAI or Azure, we recommend using GPT4.', + })} - {i18n.translate( - 'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel', - { - defaultMessage: 'Set up GenAI connector', - } - )} + {i18n.translate('xpack.aiAssistant.initialSetupPanel.setupConnector.buttonLabel', { + defaultMessage: 'Set up GenAI connector', + })}
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_knowledge_base.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/welcome_message_knowledge_base.tsx similarity index 81% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_knowledge_base.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/welcome_message_knowledge_base.tsx index afdbed9ed4c43..72653473c41ae 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_knowledge_base.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/welcome_message_knowledge_base.tsx @@ -22,8 +22,8 @@ import usePrevious from 'react-use/lib/usePrevious'; import useTimeoutFn from 'react-use/lib/useTimeoutFn'; import useInterval from 'react-use/lib/useInterval'; import { WelcomeMessageKnowledgeBaseSetupErrorPanel } from './welcome_message_knowledge_base_setup_error_panel'; -import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base'; -import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors'; +import type { UseKnowledgeBaseResult } from '../hooks/use_knowledge_base'; +import type { UseGenAIConnectorsResult } from '../hooks/use_genai_connectors'; export function WelcomeMessageKnowledgeBase({ connectors, @@ -80,13 +80,10 @@ export function WelcomeMessageKnowledgeBase({ {knowledgeBase.isInstalling ? ( <> - {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessage.weAreSettingUpTextLabel', - { - defaultMessage: - 'We are setting up your knowledge base. This may take a few minutes. You can continue to use the Assistant while this process is underway.', - } - )} + {i18n.translate('xpack.aiAssistant.welcomeMessage.weAreSettingUpTextLabel', { + defaultMessage: + 'We are setting up your knowledge base. This may take a few minutes. You can continue to use the Assistant while this process is underway.', + })} @@ -96,10 +93,9 @@ export function WelcomeMessageKnowledgeBase({ isLoading onClick={noop} > - {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel', - { defaultMessage: 'Setting up Knowledge base' } - )} + {i18n.translate('xpack.aiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel', { + defaultMessage: 'Setting up Knowledge base', + })} ) : null} @@ -112,7 +108,7 @@ export function WelcomeMessageKnowledgeBase({ <> {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel', + 'xpack.aiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel', { defaultMessage: `Your Knowledge base hasn't been set up.` } )} @@ -130,12 +126,9 @@ export function WelcomeMessageKnowledgeBase({ iconType="importAction" onClick={handleRetryInstall} > - {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessage.retryButtonLabel', - { - defaultMessage: 'Install Knowledge base', - } - )} + {i18n.translate('xpack.aiAssistant.welcomeMessage.retryButtonLabel', { + defaultMessage: 'Install Knowledge base', + })} @@ -149,7 +142,7 @@ export function WelcomeMessageKnowledgeBase({ onClick={() => setIsPopoverOpen(!isPopoverOpen)} > {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel', + 'xpack.aiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel', { defaultMessage: 'Inspect issues' } )} @@ -180,7 +173,7 @@ export function WelcomeMessageKnowledgeBase({ {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel', + 'xpack.aiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel', { defaultMessage: 'Knowledge base successfully installed' } )} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_knowledge_base_setup_error_panel.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/welcome_message_knowledge_base_setup_error_panel.tsx similarity index 80% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_knowledge_base_setup_error_panel.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/welcome_message_knowledge_base_setup_error_panel.tsx index a9a6fcff85240..eeff9c8afd7f3 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_knowledge_base_setup_error_panel.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/welcome_message_knowledge_base_setup_error_panel.tsx @@ -21,8 +21,8 @@ import { EuiPanel, } from '@elastic/eui'; import { css } from '@emotion/css'; -import { useKibana } from '../../hooks/use_kibana'; -import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base'; +import { useKibana } from '../hooks/use_kibana'; +import type { UseKnowledgeBaseResult } from '../hooks/use_knowledge_base'; const panelContainerClassName = css` width: 330px; @@ -47,10 +47,9 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({ - {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessage.issuesDescriptionListTitleLabel', - { defaultMessage: 'Issues' } - )} + {i18n.translate('xpack.aiAssistant.welcomeMessage.issuesDescriptionListTitleLabel', { + defaultMessage: 'Issues', + })} @@ -61,7 +60,7 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
  • {' '} {modelName}, @@ -75,7 +74,7 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
  • {' '} {modelName}, @@ -92,7 +91,7 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
  • {' '} {modelName}, @@ -113,7 +112,7 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({ {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel', + 'xpack.aiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel', { defaultMessage: 'Retry install' } )} @@ -133,13 +132,12 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({ - {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessage.trainedModelsLinkLabel', - { defaultMessage: 'Trained Models' } - )} + {i18n.translate('xpack.aiAssistant.welcomeMessage.trainedModelsLinkLabel', { + defaultMessage: 'Trained Models', + })} ), }} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view.tsx b/x-pack/packages/kbn-ai-assistant/src/conversation/conversation_view.tsx similarity index 71% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view.tsx rename to x-pack/packages/kbn-ai-assistant/src/conversation/conversation_view.tsx index da34c98b86fbc..260a7cb5c10ed 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/conversation/conversation_view.tsx @@ -9,44 +9,44 @@ import { css } from '@emotion/css'; import { euiThemeVars } from '@kbn/ui-theme'; import React, { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; -import { useAbortableAsync } from '@kbn/observability-ai-assistant-plugin/public'; -import { ChatBody } from '../../components/chat/chat_body'; -import { ChatInlineEditingContent } from '../../components/chat/chat_inline_edit'; -import { ConversationList } from '../../components/chat/conversation_list'; -import { useCurrentUser } from '../../hooks/use_current_user'; -import { useGenAIConnectors } from '../../hooks/use_genai_connectors'; -import { useKnowledgeBase } from '../../hooks/use_knowledge_base'; -import { useObservabilityAIAssistantParams } from '../../hooks/use_observability_ai_assistant_params'; -import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router'; -import { useObservabilityAIAssistantAppService } from '../../hooks/use_observability_ai_assistant_app_service'; -import { useKibana } from '../../hooks/use_kibana'; -import { useConversationKey } from '../../hooks/use_conversation_key'; -import { useConversationList } from '../../hooks/use_conversation_list'; +import { useKibana } from '../hooks/use_kibana'; +import { ConversationList, ChatBody, ChatInlineEditingContent } from '../chat'; +import { useConversationKey } from '../hooks/use_conversation_key'; +import { useCurrentUser } from '../hooks/use_current_user'; +import { useGenAIConnectors } from '../hooks/use_genai_connectors'; +import { useKnowledgeBase } from '../hooks/use_knowledge_base'; +import { useAIAssistantAppService } from '../hooks/use_ai_assistant_app_service'; +import { useAbortableAsync } from '../hooks/use_abortable_async'; +import { useConversationList } from '../hooks/use_conversation_list'; const SECOND_SLOT_CONTAINER_WIDTH = 400; -export function ConversationView() { +interface ConversationViewProps { + conversationId?: string; + navigateToConversation: (nextConversationId?: string) => void; + getConversationHref?: (conversationId: string) => string; + newConversationHref?: string; +} + +export const ConversationView: React.FC = ({ + conversationId, + navigateToConversation, + getConversationHref, + newConversationHref, +}) => { const { euiTheme } = useEuiTheme(); const currentUser = useCurrentUser(); - const service = useObservabilityAIAssistantAppService(); + const service = useAIAssistantAppService(); const connectors = useGenAIConnectors(); const knowledgeBase = useKnowledgeBase(); - const observabilityAIAssistantRouter = useObservabilityAIAssistantRouter(); - - const { path } = useObservabilityAIAssistantParams('/conversations/*'); - const { services: { - plugins: { - start: { - observabilityAIAssistant: { ObservabilityAIAssistantChatServiceContext }, - }, - }, + observabilityAIAssistant: { ObservabilityAIAssistantChatServiceContext }, }, } = useKibana(); @@ -57,8 +57,6 @@ export function ConversationView() { [service] ); - const conversationId = 'conversationId' in path ? path.conversationId : undefined; - const { key: bodyKey, updateConversationIdInPlace } = useConversationKey(conversationId); const [secondSlotContainer, setSecondSlotContainer] = useState(null); @@ -66,19 +64,6 @@ export function ConversationView() { const conversationList = useConversationList(); - function navigateToConversation(nextConversationId?: string) { - if (nextConversationId) { - observabilityAIAssistantRouter.push('/conversations/{conversationId}', { - path: { - conversationId: nextConversationId, - }, - query: {}, - }); - } else { - observabilityAIAssistantRouter.push('/conversations/new', { path: {}, query: {} }); - } - } - function handleRefreshConversations() { conversationList.conversations.refresh(); } @@ -153,6 +138,9 @@ export function ConversationView() { } }); }} + newConversationHref={newConversationHref} + onConversationSelect={navigateToConversation} + getConversationHref={getConversationHref} /> @@ -176,6 +164,7 @@ export function ConversationView() { knowledgeBase={knowledgeBase} showLinkToConversationsApp={false} onConversationUpdate={handleConversationUpdate} + navigateToConversation={navigateToConversation} />
    @@ -189,4 +178,4 @@ export function ConversationView() { )} ); -} +}; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_chat.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_chat.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_chat.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_chat.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversation.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversation.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation_list.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversation_list.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation_list.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversation_list.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversations.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversations.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversations.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversations.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_current_user.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_current_user.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_current_user.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_current_user.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_genai_connectors.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_genai_connectors.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_genai_connectors.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_genai_connectors.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_knowledge_base.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_knowledge_base.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_knowledge_base.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_knowledge_base.ts diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/index.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/index.ts new file mode 100644 index 0000000000000..ee630d1caec82 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './use_ai_assistant_app_service'; +export * from './use_ai_assistant_chat_service'; +export * from './use_knowledge_base'; diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_abortable_async.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_abortable_async.ts new file mode 100644 index 0000000000000..433ca877b0f62 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_abortable_async.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { isPromise } from '@kbn/std'; +import { useEffect, useMemo, useRef, useState } from 'react'; + +interface State { + error?: Error; + value?: T; + loading: boolean; +} + +export type AbortableAsyncState = (T extends Promise + ? State + : State) & { refresh: () => void }; + +export function useAbortableAsync( + fn: ({}: { signal: AbortSignal }) => T | Promise, + deps: any[], + options?: { clearValueOnNext?: boolean; defaultValue?: () => T } +): AbortableAsyncState { + const clearValueOnNext = options?.clearValueOnNext; + + const controllerRef = useRef(new AbortController()); + + const [refreshId, setRefreshId] = useState(0); + + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); + const [value, setValue] = useState(options?.defaultValue); + + useEffect(() => { + controllerRef.current.abort(); + + const controller = new AbortController(); + controllerRef.current = controller; + + if (clearValueOnNext) { + setValue(undefined); + setError(undefined); + } + + try { + const response = fn({ signal: controller.signal }); + if (isPromise(response)) { + setLoading(true); + response + .then((nextValue) => { + setError(undefined); + setValue(nextValue); + }) + .catch((err) => { + setValue(undefined); + setError(err); + }) + .finally(() => setLoading(false)); + } else { + setError(undefined); + setValue(response); + setLoading(false); + } + } catch (err) { + setValue(undefined); + setError(err); + setLoading(false); + } + + return () => { + controller.abort(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, deps.concat(refreshId, clearValueOnNext)); + + return useMemo>(() => { + return { + error, + loading, + value, + refresh: () => { + setRefreshId((id) => id + 1); + }, + } as unknown as AbortableAsyncState; + }, [error, value, loading]); +} diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.ts new file mode 100644 index 0000000000000..bb1f93079eb09 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.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 { useKibana } from './use_kibana'; + +export function useAIAssistantAppService() { + const { services } = useKibana(); + + if (!services.observabilityAIAssistant?.service) { + throw new Error( + 'AI Assistant Service is not available. Did you provide this service in your plugin contract?' + ); + } + + return services.observabilityAIAssistant.service; +} diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_chat_service.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_chat_service.ts new file mode 100644 index 0000000000000..a3eefef196901 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_chat_service.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 { useKibana } from './use_kibana'; + +export function useAIAssistantChatService() { + const { + services: { observabilityAIAssistant }, + } = useKibana(); + + return observabilityAIAssistant.useObservabilityAIAssistantChatService(); +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_confirm_modal.tsx b/x-pack/packages/kbn-ai-assistant/src/hooks/use_confirm_modal.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_confirm_modal.tsx rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_confirm_modal.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.test.tsx b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx similarity index 94% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.test.tsx rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx index 150847a011207..4c4ced36c8796 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.test.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx @@ -19,9 +19,8 @@ import { StreamingChatResponseEventType, StreamingChatResponseEventWithoutError, } from '@kbn/observability-ai-assistant-plugin/common'; -import { ObservabilityAIAssistantAppServiceProvider } from '../context/observability_ai_assistant_app_service_provider'; import { EMPTY_CONVERSATION_TITLE } from '../i18n'; -import type { ObservabilityAIAssistantAppService } from '../service/create_app_service'; +import type { AIAssistantAppService } from '../service/create_app_service'; import { useConversation, type UseConversationProps, @@ -35,9 +34,9 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; let hookResult: RenderHookResult; -type MockedService = DeeplyMockedKeys> & { +type MockedService = DeeplyMockedKeys> & { conversations: DeeplyMockedKeys< - Omit + Omit > & { predefinedConversation$: Observable; }; @@ -66,18 +65,15 @@ const useKibanaMockServices = { uiSettings: { get: jest.fn(), }, - plugins: { - start: { - observabilityAIAssistant: { - useChat: createUseChat({ - notifications: { - toasts: { - addError: addErrorMock, - }, - } as unknown as NotificationsStart, - }), - }, - }, + observabilityAIAssistant: { + useChat: createUseChat({ + notifications: { + toasts: { + addError: addErrorMock, + }, + } as unknown as NotificationsStart, + }), + service: mockService, }, }; @@ -87,11 +83,7 @@ describe('useConversation', () => { beforeEach(() => { jest.clearAllMocks(); wrapper = ({ children }: PropsWithChildren) => ( - - - {children} - - + {children} ); }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts similarity index 91% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts index 617b1b302473f..744e071d5b1ba 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts @@ -12,16 +12,14 @@ import type { ConversationCreateRequest, Message, } from '@kbn/observability-ai-assistant-plugin/common'; -import { - ObservabilityAIAssistantChatService, - useAbortableAsync, -} from '@kbn/observability-ai-assistant-plugin/public'; +import type { ObservabilityAIAssistantChatService } from '@kbn/observability-ai-assistant-plugin/public'; import type { AbortableAsyncState } from '@kbn/observability-ai-assistant-plugin/public'; import type { UseChatResult } from '@kbn/observability-ai-assistant-plugin/public'; import { EMPTY_CONVERSATION_TITLE } from '../i18n'; +import { useAIAssistantAppService } from './use_ai_assistant_app_service'; import { useKibana } from './use_kibana'; import { useOnce } from './use_once'; -import { useObservabilityAIAssistantAppService } from './use_observability_ai_assistant_app_service'; +import { useAbortableAsync } from './use_abortable_async'; function createNewConversation({ title = EMPTY_CONVERSATION_TITLE, @@ -62,17 +60,13 @@ export function useConversation({ connectorId, onConversationUpdate, }: UseConversationProps): UseConversationResult { - const service = useObservabilityAIAssistantAppService(); + const service = useAIAssistantAppService(); const { scope } = service; const { services: { notifications, - plugins: { - start: { - observabilityAIAssistant: { useChat }, - }, - }, + observabilityAIAssistant: { useChat }, }, } = useKibana(); @@ -106,8 +100,8 @@ export function useConversation({ }, }) .catch((err) => { - notifications.toasts.addError(err, { - title: i18n.translate('xpack.observabilityAiAssistant.errorUpdatingConversation', { + notifications!.toasts.addError(err, { + title: i18n.translate('xpack.aiAssistant.errorUpdatingConversation', { defaultMessage: 'Could not update conversation', }), }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation_key.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation_key.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation_key.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation_key.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation_list.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation_list.ts similarity index 86% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation_list.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation_list.ts index 6fa6bc02e7b35..d0db7665a30b6 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation_list.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation_list.ts @@ -12,9 +12,8 @@ import { type Conversation, useAbortableAsync, } from '@kbn/observability-ai-assistant-plugin/public'; +import { useAIAssistantAppService } from './use_ai_assistant_app_service'; import { useKibana } from './use_kibana'; -import { useObservabilityAIAssistantAppService } from './use_observability_ai_assistant_app_service'; - export interface UseConversationListResult { isLoading: boolean; conversations: AbortableAsyncState<{ conversations: Conversation[] }>; @@ -22,7 +21,7 @@ export interface UseConversationListResult { } export function useConversationList(): UseConversationListResult { - const service = useObservabilityAIAssistantAppService(); + const service = useAIAssistantAppService(); const [isUpdatingList, setIsUpdatingList] = useState(false); @@ -62,8 +61,8 @@ export function useConversationList(): UseConversationListResult { conversations.refresh(); } catch (err) { - notifications.toasts.addError(err, { - title: i18n.translate('xpack.observabilityAiAssistant.flyout.failedToDeleteConversation', { + notifications!.toasts.addError(err, { + title: i18n.translate('xpack.aiAssistant.flyout.failedToDeleteConversation', { defaultMessage: 'Could not delete conversation', }), }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_current_user.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_current_user.ts similarity index 84% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_current_user.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_current_user.ts index 82c13eb876117..c169358653a49 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_current_user.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_current_user.ts @@ -5,9 +5,9 @@ * 2.0. */ +import { useKibana } from '@kbn/kibana-react-plugin/public'; import { AuthenticatedUser } from '@kbn/security-plugin/common'; import { useEffect, useState } from 'react'; -import { useKibana } from './use_kibana'; export function useCurrentUser() { const { @@ -19,7 +19,7 @@ export function useCurrentUser() { useEffect(() => { const getCurrentUser = async () => { try { - const authenticatedUser = await security.authc.getCurrentUser(); + const authenticatedUser = await security!.authc.getCurrentUser(); setUser(authenticatedUser); } catch { setUser(undefined); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_genai_connectors.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_genai_connectors.ts similarity index 66% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_genai_connectors.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_genai_connectors.ts index 1b105513a2323..642bf9488f186 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_genai_connectors.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_genai_connectors.ts @@ -5,16 +5,13 @@ * 2.0. */ -import { useKibana } from './use_kibana'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { AIAssistantPluginStartDependencies } from '../types'; export function useGenAIConnectors() { const { - services: { - plugins: { - start: { observabilityAIAssistant }, - }, - }, - } = useKibana(); + services: { observabilityAIAssistant }, + } = useKibana(); return observabilityAIAssistant.useGenAIConnectors(); } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_json_editor_model.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_json_editor_model.ts similarity index 92% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_json_editor_model.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_json_editor_model.ts index 6f4535d84acef..1b14c504d935d 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_json_editor_model.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_json_editor_model.ts @@ -7,7 +7,7 @@ import { useEffect, useMemo, useState } from 'react'; import { monaco } from '@kbn/monaco'; import { createInitializedObject } from '../utils/create_initialized_object'; -import { useObservabilityAIAssistantChatService } from './use_observability_ai_assistant_chat_service'; +import { useAIAssistantChatService } from './use_ai_assistant_chat_service'; import { safeJsonParse } from '../utils/safe_json_parse'; const { editor, languages, Uri } = monaco; @@ -19,7 +19,7 @@ export const useJsonEditorModel = ({ functionName: string | undefined; initialJson?: string | undefined; }) => { - const chatService = useObservabilityAIAssistantChatService(); + const chatService = useAIAssistantChatService(); const functionDefinition = chatService.getFunctions().find((func) => func.name === functionName); diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_kibana.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_kibana.ts new file mode 100644 index 0000000000000..44aec48a06467 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_kibana.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 { useKibana } from '@kbn/kibana-react-plugin/public'; +import { AIAssistantPluginStartDependencies } from '../types'; + +const useTypedKibana = () => useKibana(); + +export { useTypedKibana as useKibana }; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_knowledge_base.tsx b/x-pack/packages/kbn-ai-assistant/src/hooks/use_knowledge_base.tsx similarity index 82% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_knowledge_base.tsx rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_knowledge_base.tsx index bca9b38485695..0b949fcdbff0e 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_knowledge_base.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_knowledge_base.tsx @@ -15,7 +15,7 @@ import { useAbortableAsync, } from '@kbn/observability-ai-assistant-plugin/public'; import { useKibana } from './use_kibana'; -import { useObservabilityAIAssistantAppService } from './use_observability_ai_assistant_app_service'; +import { useAIAssistantAppService } from './use_ai_assistant_app_service'; export interface UseKnowledgeBaseResult { status: AbortableAsyncState<{ @@ -31,13 +31,8 @@ export interface UseKnowledgeBaseResult { } export function useKnowledgeBase(): UseKnowledgeBaseResult { - const { - notifications: { toasts }, - plugins: { - start: { ml }, - }, - } = useKibana().services; - const service = useObservabilityAIAssistantAppService(); + const { notifications, ml } = useKibana().services; + const service = useAIAssistantAppService(); const status = useAbortableAsync( ({ signal }) => { @@ -75,8 +70,8 @@ export function useKnowledgeBase(): UseKnowledgeBaseResult { return install(); } setInstallError(error); - toasts.addError(error, { - title: i18n.translate('xpack.observabilityAiAssistant.errorSettingUpKnowledgeBase', { + notifications!.toasts.addError(error, { + title: i18n.translate('xpack.aiAssistant.errorSettingUpKnowledgeBase', { defaultMessage: 'Could not set up Knowledge Base', }), }); @@ -92,5 +87,5 @@ export function useKnowledgeBase(): UseKnowledgeBaseResult { isInstalling, installError, }; - }, [status, isInstalling, installError, service, ml.mlApi?.savedObjects, toasts]); + }, [status, isInstalling, installError, service, ml, notifications]); } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_last_used_prompts.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_last_used_prompts.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_last_used_prompts.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_last_used_prompts.ts diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_license.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_license.ts new file mode 100644 index 0000000000000..6d146274c7f4d --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_license.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ILicense, LicenseType } from '@kbn/licensing-plugin/public'; +import { useCallback } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { useKibana } from './use_kibana'; + +interface UseLicenseReturnValue { + getLicense: () => ILicense | null; + hasAtLeast: (level: LicenseType) => boolean | undefined; +} + +export const useLicense = (): UseLicenseReturnValue => { + const { + services: { licensing }, + } = useKibana(); + + const license = useObservable(licensing.license$); + + return { + getLicense: () => license ?? null, + hasAtLeast: useCallback( + (level: LicenseType) => { + if (!license) return; + + return !!license && license.isAvailable && license.isActive && license.hasAtLeast(level); + }, + [license] + ), + }; +}; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_license_management_locator.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_license_management_locator.ts similarity index 89% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_license_management_locator.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_license_management_locator.ts index 1d5dd04203352..7e650affa2ca5 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_license_management_locator.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_license_management_locator.ts @@ -11,11 +11,7 @@ const LICENSE_MANAGEMENT_LOCATOR = 'LICENSE_MANAGEMENT_LOCATOR'; export const useLicenseManagementLocator = () => { const { - services: { - plugins: { - start: { share }, - }, - }, + services: { share }, } = useKibana(); const locator = share.url.locators.get(LICENSE_MANAGEMENT_LOCATOR); diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_local_storage.test.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_local_storage.test.ts new file mode 100644 index 0000000000000..ab1d00392fdb9 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_local_storage.test.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 { renderHook, act } from '@testing-library/react-hooks'; +import { useLocalStorage } from './use_local_storage'; + +describe('useLocalStorage', () => { + const key = 'testKey'; + const defaultValue = 'defaultValue'; + + beforeEach(() => { + localStorage.clear(); + }); + + it('should return the default value when local storage is empty', () => { + const { result } = renderHook(() => useLocalStorage(key, defaultValue)); + const [item] = result.current; + + expect(item).toBe(defaultValue); + }); + + it('should return the stored value when local storage has a value', () => { + const storedValue = 'storedValue'; + localStorage.setItem(key, JSON.stringify(storedValue)); + const { result } = renderHook(() => useLocalStorage(key, defaultValue)); + const [item] = result.current; + + expect(item).toBe(storedValue); + }); + + it('should save the value to local storage', () => { + const { result } = renderHook(() => useLocalStorage(key, defaultValue)); + const [, saveToStorage] = result.current; + const newValue = 'newValue'; + + act(() => { + saveToStorage(newValue); + }); + + expect(JSON.parse(localStorage.getItem(key) || '')).toBe(newValue); + }); + + it('should remove the value from local storage when the value is undefined', () => { + const { result } = renderHook(() => useLocalStorage(key, defaultValue)); + const [, saveToStorage] = result.current; + + act(() => { + saveToStorage(undefined as unknown as string); + }); + + expect(localStorage.getItem(key)).toBe(null); + }); + + it('should listen for storage events to window, and remove the listener upon unmount', () => { + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); + + const { unmount } = renderHook(() => useLocalStorage(key, defaultValue)); + + expect(addEventListenerSpy).toHaveBeenCalled(); + + const eventTypes = addEventListenerSpy.mock.calls; + + expect(eventTypes).toContainEqual(['storage', expect.any(Function)]); + + unmount(); + + expect(removeEventListenerSpy).toHaveBeenCalled(); + + addEventListenerSpy.mockRestore(); + removeEventListenerSpy.mockRestore(); + }); +}); diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_local_storage.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_local_storage.ts new file mode 100644 index 0000000000000..ea9e13163e4b0 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_local_storage.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useState, useEffect, useMemo, useCallback } from 'react'; + +export function useLocalStorage(key: string, defaultValue: T) { + // This is necessary to fix a race condition issue. + // It guarantees that the latest value will be always returned after the value is updated + const [storageUpdate, setStorageUpdate] = useState(0); + + const item = useMemo(() => { + return getFromStorage(key, defaultValue); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [key, storageUpdate, defaultValue]); + + const saveToStorage = useCallback( + (value: T) => { + if (value === undefined) { + window.localStorage.removeItem(key); + } else { + window.localStorage.setItem(key, JSON.stringify(value)); + setStorageUpdate(storageUpdate + 1); + } + }, + [key, storageUpdate] + ); + + useEffect(() => { + function onUpdate(event: StorageEvent) { + if (event.key === key) { + setStorageUpdate(storageUpdate + 1); + } + } + window.addEventListener('storage', onUpdate); + return () => { + window.removeEventListener('storage', onUpdate); + }; + }, [key, setStorageUpdate, storageUpdate]); + + return useMemo(() => [item, saveToStorage] as const, [item, saveToStorage]); +} + +function getFromStorage(keyName: string, defaultValue: T) { + const storedItem = window.localStorage.getItem(keyName); + + if (storedItem !== null) { + try { + return JSON.parse(storedItem) as T; + } catch (err) { + window.localStorage.removeItem(keyName); + // eslint-disable-next-line no-console + console.log(`Unable to decode: ${keyName}`); + } + } + return defaultValue; +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_once.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_once.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_once.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_once.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_simulated_function_calling.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_simulated_function_calling.ts similarity index 89% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_simulated_function_calling.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_simulated_function_calling.ts index 4d441b03a3ddc..4515f2126dbfd 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_simulated_function_calling.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_simulated_function_calling.ts @@ -13,7 +13,7 @@ export function useSimulatedFunctionCalling() { services: { uiSettings }, } = useKibana(); - const simulatedFunctionCallingEnabled = uiSettings.get( + const simulatedFunctionCallingEnabled = uiSettings!.get( aiAssistantSimulatedFunctionCalling, false ); diff --git a/x-pack/packages/kbn-ai-assistant/src/i18n.ts b/x-pack/packages/kbn-ai-assistant/src/i18n.ts new file mode 100644 index 0000000000000..5c5be1633a07a --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/i18n.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 ASSISTANT_SETUP_TITLE = i18n.translate('xpack.aiAssistant.assistantSetup.title', { + defaultMessage: 'Welcome to the Elastic AI Assistant', +}); + +export const EMPTY_CONVERSATION_TITLE = i18n.translate('xpack.aiAssistant.emptyConversationTitle', { + defaultMessage: 'New conversation', +}); + +export const UPGRADE_LICENSE_TITLE = i18n.translate('xpack.aiAssistant.incorrectLicense.title', { + defaultMessage: 'Upgrade your license', +}); diff --git a/x-pack/packages/kbn-ai-assistant/src/index.ts b/x-pack/packages/kbn-ai-assistant/src/index.ts new file mode 100644 index 0000000000000..ba2265e88715f --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './conversation/conversation_view'; +export * from './service/create_app_service'; +export * from './hooks'; +export * from './chat'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/prompt_editor/prompt_editor.stories.tsx similarity index 96% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/prompt_editor/prompt_editor.stories.tsx index f951653b152cc..ed2948e50f15e 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/prompt_editor/prompt_editor.stories.tsx @@ -8,13 +8,13 @@ import React from 'react'; import { ComponentStory, ComponentStoryObj } from '@storybook/react'; import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public'; +import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories'; import { PromptEditor as Component, PromptEditorProps } from './prompt_editor'; -import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories'; /* JSON Schema validation in the PromptEditor compponent does not work when rendering the component from within Storybook. - + */ export default { component: Component, diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.tsx b/x-pack/packages/kbn-ai-assistant/src/prompt_editor/prompt_editor.tsx similarity index 97% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.tsx rename to x-pack/packages/kbn-ai-assistant/src/prompt_editor/prompt_editor.tsx index db7f3a8f11888..cc2fe761d6176 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/prompt_editor/prompt_editor.tsx @@ -14,10 +14,10 @@ import { type TelemetryEventTypeWithPayload, ObservabilityAIAssistantTelemetryEventType, } from '@kbn/observability-ai-assistant-plugin/public'; +import { useLastUsedPrompts } from '../hooks/use_last_used_prompts'; import { FunctionListPopover } from '../chat/function_list_popover'; import { PromptEditorFunction } from './prompt_editor_function'; import { PromptEditorNaturalLanguage } from './prompt_editor_natural_language'; -import { useLastUsedPrompts } from '../../hooks/use_last_used_prompts'; export interface PromptEditorProps { disabled: boolean; @@ -194,7 +194,7 @@ export function PromptEditor({ {functionName} {chatService.renderFunction(props.name, props.arguments, props.response, props.onActionClick)} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/service/create_app_service.ts b/x-pack/packages/kbn-ai-assistant/src/service/create_app_service.ts similarity index 63% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/service/create_app_service.ts rename to x-pack/packages/kbn-ai-assistant/src/service/create_app_service.ts index dfb9b703bc4ed..bd01ab39a6d5c 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/service/create_app_service.ts +++ b/x-pack/packages/kbn-ai-assistant/src/service/create_app_service.ts @@ -6,15 +6,15 @@ */ import type { ObservabilityAIAssistantService } from '@kbn/observability-ai-assistant-plugin/public'; -import type { ObservabilityAIAssistantAppPluginStartDependencies } from '../types'; +import { AIAssistantPluginStartDependencies } from '../types'; -export type ObservabilityAIAssistantAppService = ObservabilityAIAssistantService; +export type AIAssistantAppService = ObservabilityAIAssistantService; export function createAppService({ pluginsStart, }: { - pluginsStart: ObservabilityAIAssistantAppPluginStartDependencies; -}): ObservabilityAIAssistantAppService { + pluginsStart: AIAssistantPluginStartDependencies; +}): AIAssistantAppService { return { ...pluginsStart.observabilityAIAssistant.service, }; diff --git a/x-pack/packages/kbn-ai-assistant/src/types/index.ts b/x-pack/packages/kbn-ai-assistant/src/types/index.ts new file mode 100644 index 0000000000000..afebbafd7e643 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/types/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; +import type { MlPluginStart } from '@kbn/ml-plugin/public'; +import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; + +export interface AIAssistantPluginStartDependencies { + licensing: LicensingPluginStart; + ml: MlPluginStart; + observabilityAIAssistant: ObservabilityAIAssistantPublicStart; + share: SharePluginStart; + triggersActionsUi: TriggersAndActionsUIPublicPluginStart; +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/builders.ts b/x-pack/packages/kbn-ai-assistant/src/utils/builders.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/builders.ts rename to x-pack/packages/kbn-ai-assistant/src/utils/builders.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_initialized_object.test.ts b/x-pack/packages/kbn-ai-assistant/src/utils/create_initialized_object.test.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_initialized_object.test.ts rename to x-pack/packages/kbn-ai-assistant/src/utils/create_initialized_object.test.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_initialized_object.ts b/x-pack/packages/kbn-ai-assistant/src/utils/create_initialized_object.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_initialized_object.ts rename to x-pack/packages/kbn-ai-assistant/src/utils/create_initialized_object.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_mock_chat_service.ts b/x-pack/packages/kbn-ai-assistant/src/utils/create_mock_chat_service.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_mock_chat_service.ts rename to x-pack/packages/kbn-ai-assistant/src/utils/create_mock_chat_service.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_role_translation.ts b/x-pack/packages/kbn-ai-assistant/src/utils/get_role_translation.ts similarity index 61% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_role_translation.ts rename to x-pack/packages/kbn-ai-assistant/src/utils/get_role_translation.ts index f74c9f842e402..95421a089dea0 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_role_translation.ts +++ b/x-pack/packages/kbn-ai-assistant/src/utils/get_role_translation.ts @@ -10,21 +10,18 @@ import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public'; export function getRoleTranslation(role: MessageRole) { if (role === MessageRole.User) { - return i18n.translate('xpack.observabilityAiAssistant.chatTimeline.messages.user.label', { + return i18n.translate('xpack.aiAssistant.chatTimeline.messages.user.label', { defaultMessage: 'You', }); } if (role === MessageRole.System) { - return i18n.translate('xpack.observabilityAiAssistant.chatTimeline.messages.system.label', { + return i18n.translate('xpack.aiAssistant.chatTimeline.messages.system.label', { defaultMessage: 'System', }); } - return i18n.translate( - 'xpack.observabilityAiAssistant.chatTimeline.messages.elasticAssistant.label', - { - defaultMessage: 'Elastic Assistant', - } - ); + return i18n.translate('xpack.aiAssistant.chatTimeline.messages.elasticAssistant.label', { + defaultMessage: 'Elastic Assistant', + }); } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_timeline_items_from_conversation.test.tsx b/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx similarity index 98% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_timeline_items_from_conversation.test.tsx rename to x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx index 6fb7e1a323d08..337c11419209e 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_timeline_items_from_conversation.test.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx @@ -23,12 +23,8 @@ function Providers({ children }: { children: React.ReactElement }) { mockChatService, - }, - }, + observabilityAIAssistant: { + useObservabilityAIAssistantChatService: () => mockChatService, }, }} > diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_timeline_items_from_conversation.tsx b/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.tsx similarity index 94% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_timeline_items_from_conversation.tsx rename to x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.tsx index 9a3fed770b944..999ac4f095025 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_timeline_items_from_conversation.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.tsx @@ -18,9 +18,9 @@ import { ObservabilityAIAssistantChatService, } from '@kbn/observability-ai-assistant-plugin/public'; import type { ChatActionClickPayload } from '@kbn/observability-ai-assistant-plugin/public'; -import type { ChatTimelineItem } from '../components/chat/chat_timeline'; -import { RenderFunction } from '../components/render_function'; +import { RenderFunction } from '../render_function'; import { safeJsonParse } from './safe_json_parse'; +import type { ChatTimelineItem } from '../chat/chat_timeline'; function convertMessageToMarkdownCodeBlock(message: Message['message']) { let value: object; @@ -95,7 +95,7 @@ export function getTimelineItemsfromConversation({ '@timestamp': new Date().toISOString(), message: { role: MessageRole.User }, }, - title: i18n.translate('xpack.observabilityAiAssistant.conversationStartTitle', { + title: i18n.translate('xpack.aiAssistant.conversationStartTitle', { defaultMessage: 'started a conversation', }), role: MessageRole.User, @@ -149,7 +149,7 @@ export function getTimelineItemsfromConversation({ title = !isError ? ( , @@ -157,7 +157,7 @@ export function getTimelineItemsfromConversation({ /> ) : ( , @@ -189,7 +189,7 @@ export function getTimelineItemsfromConversation({ // User suggested a function title = ( , @@ -222,7 +222,7 @@ export function getTimelineItemsfromConversation({ if (message.message.function_call?.name) { title = ( , diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_settings_href.ts b/x-pack/packages/kbn-ai-assistant/src/utils/non_nullable.ts similarity index 57% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_settings_href.ts rename to x-pack/packages/kbn-ai-assistant/src/utils/non_nullable.ts index 45a3083d66327..8618e44dbb823 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_settings_href.ts +++ b/x-pack/packages/kbn-ai-assistant/src/utils/non_nullable.ts @@ -5,8 +5,6 @@ * 2.0. */ -import { HttpStart } from '@kbn/core/public'; - -export function getSettingsHref(http: HttpStart) { - return http!.basePath.prepend(`/app/management/kibana/observabilityAiAssistantManagement`); +export function nonNullable(v: T): v is NonNullable { + return v !== null && v !== undefined; } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_settings_kb_href.ts b/x-pack/packages/kbn-ai-assistant/src/utils/safe_json_parse.ts similarity index 52% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_settings_kb_href.ts rename to x-pack/packages/kbn-ai-assistant/src/utils/safe_json_parse.ts index 2aa625da08d17..a4f2dfa5c2503 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_settings_kb_href.ts +++ b/x-pack/packages/kbn-ai-assistant/src/utils/safe_json_parse.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { HttpStart } from '@kbn/core/public'; - -export function getSettingsKnowledgeBaseHref(http: HttpStart) { - return http!.basePath.prepend( - `/app/management/kibana/observabilityAiAssistantManagement?tab=knowledge_base` - ); +export function safeJsonParse(jsonStr: string) { + try { + return JSON.parse(jsonStr); + } catch (err) { + return jsonStr; + } } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/storybook_decorator.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/utils/storybook_decorator.stories.tsx similarity index 57% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/storybook_decorator.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/utils/storybook_decorator.stories.tsx index 9dc2e7057b951..d6292803b42af 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/storybook_decorator.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/utils/storybook_decorator.stories.tsx @@ -13,10 +13,9 @@ import { } from '@kbn/observability-ai-assistant-plugin/public'; import { Subject } from 'rxjs'; import { coreMock } from '@kbn/core/public/mocks'; -import { ObservabilityAIAssistantAppService } from '../service/create_app_service'; -import { ObservabilityAIAssistantAppServiceProvider } from '../context/observability_ai_assistant_app_service_provider'; +import { AIAssistantAppService } from '../service/create_app_service'; -const mockService: ObservabilityAIAssistantAppService = { +const mockService: AIAssistantAppService = { ...createStorybookService(), }; @@ -38,25 +37,17 @@ export function KibanaReactStorybookDecorator(Story: ComponentType) { licensing: { license$: new Subject(), }, - // observabilityAIAssistant: { - // ObservabilityAIAssistantChatServiceContext, - // ObservabilityAIAssistantMultipaneFlyoutContext, - // }, - plugins: { - start: { - observabilityAIAssistant: { - ObservabilityAIAssistantMultipaneFlyoutContext, - }, - triggersActionsUi: { getAddRuleFlyout: {}, getAddConnectorFlyout: {} }, - }, + observabilityAIAssistant: { + ObservabilityAIAssistantChatServiceContext, + ObservabilityAIAssistantMultipaneFlyoutContext, + service: mockService, }, + triggersActionsUi: { getAddRuleFlyout: {}, getAddConnectorFlyout: {} }, }} > - - - - - + + + ); } diff --git a/x-pack/packages/kbn-ai-assistant/tsconfig.json b/x-pack/packages/kbn-ai-assistant/tsconfig.json new file mode 100644 index 0000000000000..c8d91c9d37450 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/tsconfig.json @@ -0,0 +1,39 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + "@emotion/react/types/css-prop" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/core-http-browser", + "@kbn/i18n", + "@kbn/triggers-actions-ui-plugin", + "@kbn/actions-plugin", + "@kbn/i18n-react", + "@kbn/ui-theme", + "@kbn/core", + "@kbn/observability-ai-assistant-plugin", + "@kbn/security-plugin", + "@kbn/user-profile-components", + "@kbn/std", + "@kbn/utility-types-jest", + "@kbn/kibana-react-plugin", + "@kbn/monaco", + "@kbn/licensing-plugin", + "@kbn/code-editor", + "@kbn/ml-plugin", + "@kbn/share-plugin", + ] +} diff --git a/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/chat_vertex.test.ts b/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/chat_vertex.test.ts index 37506922ff69b..07fe252bd5074 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/chat_vertex.test.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/chat_vertex.test.ts @@ -12,6 +12,7 @@ import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/act import { BaseMessage, HumanMessage, SystemMessage } from '@langchain/core/messages'; import { ActionsClientChatVertexAI } from './chat_vertex'; import { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager'; +import { GeminiContent } from '@langchain/google-common'; const connectorId = 'mock-connector-id'; @@ -54,8 +55,10 @@ const mockStreamExecute = jest.fn().mockImplementation(() => { }; }); +const systemInstruction = 'Answer the following questions truthfully and as best you can.'; + const callMessages = [ - new SystemMessage('Answer the following questions truthfully and as best you can.'), + new SystemMessage(systemInstruction), new HumanMessage('Question: Do you know my name?\n\n'), ] as unknown as BaseMessage[]; @@ -196,4 +199,32 @@ describe('ActionsClientChatVertexAI', () => { expect(handleLLMNewToken).toHaveBeenCalledWith('token3'); }); }); + + describe('message formatting', () => { + it('Properly sorts out the system role', async () => { + const actionsClientChatVertexAI = new ActionsClientChatVertexAI(defaultArgs); + + await actionsClientChatVertexAI._generate(callMessages, callOptions, callRunManager); + const params = actionsClient.execute.mock.calls[0][0].params.subActionParams as unknown as { + messages: GeminiContent[]; + systemInstruction: string; + }; + expect(params.messages.length).toEqual(1); + expect(params.messages[0].parts.length).toEqual(1); + expect(params.systemInstruction).toEqual(systemInstruction); + }); + it('Handles 2 messages in a row from the same role', async () => { + const actionsClientChatVertexAI = new ActionsClientChatVertexAI(defaultArgs); + + await actionsClientChatVertexAI._generate( + [...callMessages, new HumanMessage('Oh boy, another')], + callOptions, + callRunManager + ); + const { messages } = actionsClient.execute.mock.calls[0][0].params + .subActionParams as unknown as { messages: GeminiContent[] }; + expect(messages.length).toEqual(1); + expect(messages[0].parts.length).toEqual(2); + }); + }); }); diff --git a/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/connection.ts b/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/connection.ts index 0340d71b438db..dd3c1e1abdda0 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/connection.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/connection.ts @@ -7,6 +7,7 @@ import { ChatConnection, + GeminiContent, GoogleAbstractedClient, GoogleAIBaseLLMInput, GoogleLLMResponse, @@ -39,6 +40,22 @@ export class ActionsClientChatConnection extends ChatConnection { this.caller = caller; this.#model = fields.model; this.temperature = fields.temperature ?? 0; + const nativeFormatData = this.formatData.bind(this); + this.formatData = async (data, options) => { + const result = await nativeFormatData(data, options); + if (result?.contents != null && result?.contents.length) { + // ensure there are not 2 messages in a row from the same role, + // if there are combine them + result.contents = result.contents.reduce((acc: GeminiContent[], currentEntry) => { + if (currentEntry.role === acc[acc.length - 1]?.role) { + acc[acc.length - 1].parts = acc[acc.length - 1].parts.concat(currentEntry.parts); + return acc; + } + return [...acc, currentEntry]; + }, []); + } + return result; + }; } async _request( diff --git a/x-pack/packages/observability/logs_overview/README.md b/x-pack/packages/observability/logs_overview/README.md new file mode 100644 index 0000000000000..20d3f0f02b7df --- /dev/null +++ b/x-pack/packages/observability/logs_overview/README.md @@ -0,0 +1,3 @@ +# @kbn/observability-logs-overview + +Empty package generated by @kbn/generate diff --git a/x-pack/packages/observability/logs_overview/index.ts b/x-pack/packages/observability/logs_overview/index.ts new file mode 100644 index 0000000000000..057d1d3acd152 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { + LogsOverview, + LogsOverviewErrorContent, + LogsOverviewLoadingContent, + type LogsOverviewDependencies, + type LogsOverviewErrorContentProps, + type LogsOverviewProps, +} from './src/components/logs_overview'; +export type { + DataViewLogsSourceConfiguration, + IndexNameLogsSourceConfiguration, + LogsSourceConfiguration, + SharedSettingLogsSourceConfiguration, +} from './src/utils/logs_source'; diff --git a/x-pack/packages/observability/logs_overview/jest.config.js b/x-pack/packages/observability/logs_overview/jest.config.js new file mode 100644 index 0000000000000..2ee88ee990253 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/jest.config.js @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/x-pack/packages/observability/logs_overview'], +}; diff --git a/x-pack/packages/observability/logs_overview/kibana.jsonc b/x-pack/packages/observability/logs_overview/kibana.jsonc new file mode 100644 index 0000000000000..90b3375086720 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-browser", + "id": "@kbn/observability-logs-overview", + "owner": "@elastic/obs-ux-logs-team" +} diff --git a/x-pack/packages/observability/logs_overview/package.json b/x-pack/packages/observability/logs_overview/package.json new file mode 100644 index 0000000000000..77a529e7e59f7 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/observability-logs-overview", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0", + "sideEffects": false +} diff --git a/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx b/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx new file mode 100644 index 0000000000000..fe108289985a9 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx @@ -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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { EuiButton } from '@elastic/eui'; +import type { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; +import { FilterStateStore, buildCustomFilter } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; +import { getRouterLinkProps } from '@kbn/router-utils'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import React, { useCallback, useMemo } from 'react'; +import type { IndexNameLogsSourceConfiguration } from '../../utils/logs_source'; + +export interface DiscoverLinkProps { + documentFilters?: QueryDslQueryContainer[]; + logsSource: IndexNameLogsSourceConfiguration; + timeRange: { + start: string; + end: string; + }; + dependencies: DiscoverLinkDependencies; +} + +export interface DiscoverLinkDependencies { + share: SharePluginStart; +} + +export const DiscoverLink = React.memo( + ({ dependencies: { share }, documentFilters, logsSource, timeRange }: DiscoverLinkProps) => { + const discoverLocatorParams = useMemo( + () => ({ + dataViewSpec: { + id: logsSource.indexName, + name: logsSource.indexName, + title: logsSource.indexName, + timeFieldName: logsSource.timestampField, + }, + timeRange: { + from: timeRange.start, + to: timeRange.end, + }, + filters: documentFilters?.map((filter) => + buildCustomFilter( + logsSource.indexName, + filter, + false, + false, + categorizedLogsFilterLabel, + FilterStateStore.APP_STATE + ) + ), + }), + [ + documentFilters, + logsSource.indexName, + logsSource.timestampField, + timeRange.end, + timeRange.start, + ] + ); + + const discoverLocator = useMemo( + () => share.url.locators.get('DISCOVER_APP_LOCATOR'), + [share.url.locators] + ); + + const discoverUrl = useMemo( + () => discoverLocator?.getRedirectUrl(discoverLocatorParams), + [discoverLocatorParams, discoverLocator] + ); + + const navigateToDiscover = useCallback(() => { + discoverLocator?.navigate(discoverLocatorParams); + }, [discoverLocatorParams, discoverLocator]); + + const discoverLinkProps = getRouterLinkProps({ + href: discoverUrl, + onClick: navigateToDiscover, + }); + + return ( + + {discoverLinkTitle} + + ); + } +); + +export const discoverLinkTitle = i18n.translate( + 'xpack.observabilityLogsOverview.discoverLinkTitle', + { + defaultMessage: 'Open in Discover', + } +); + +export const categorizedLogsFilterLabel = i18n.translate( + 'xpack.observabilityLogsOverview.categorizedLogsFilterLabel', + { + defaultMessage: 'Categorized log entries', + } +); diff --git a/x-pack/plugins/security_solution_serverless/server/common/services/index.ts b/x-pack/packages/observability/logs_overview/src/components/discover_link/index.ts similarity index 79% rename from x-pack/plugins/security_solution_serverless/server/common/services/index.ts rename to x-pack/packages/observability/logs_overview/src/components/discover_link/index.ts index a76f6359f7e5b..738bf51d4529d 100644 --- a/x-pack/plugins/security_solution_serverless/server/common/services/index.ts +++ b/x-pack/packages/observability/logs_overview/src/components/discover_link/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { usageReportingService } from './usage_reporting_service'; +export * from './discover_link'; diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/index.ts b/x-pack/packages/observability/logs_overview/src/components/log_categories/index.ts new file mode 100644 index 0000000000000..786475396237c --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './log_categories'; diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx new file mode 100644 index 0000000000000..6204667827281 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { ISearchGeneric } from '@kbn/search-types'; +import { createConsoleInspector } from '@kbn/xstate-utils'; +import { useMachine } from '@xstate5/react'; +import React, { useCallback } from 'react'; +import { + categorizeLogsService, + createCategorizeLogsServiceImplementations, +} from '../../services/categorize_logs_service'; +import { IndexNameLogsSourceConfiguration } from '../../utils/logs_source'; +import { LogCategoriesErrorContent } from './log_categories_error_content'; +import { LogCategoriesLoadingContent } from './log_categories_loading_content'; +import { + LogCategoriesResultContent, + LogCategoriesResultContentDependencies, +} from './log_categories_result_content'; + +export interface LogCategoriesProps { + dependencies: LogCategoriesDependencies; + documentFilters?: QueryDslQueryContainer[]; + logsSource: IndexNameLogsSourceConfiguration; + // The time range could be made optional if we want to support an internal + // time range picker + timeRange: { + start: string; + end: string; + }; +} + +export type LogCategoriesDependencies = LogCategoriesResultContentDependencies & { + search: ISearchGeneric; +}; + +export const LogCategories: React.FC = ({ + dependencies, + documentFilters = [], + logsSource, + timeRange, +}) => { + const [categorizeLogsServiceState, sendToCategorizeLogsService] = useMachine( + categorizeLogsService.provide( + createCategorizeLogsServiceImplementations({ search: dependencies.search }) + ), + { + inspect: consoleInspector, + input: { + index: logsSource.indexName, + startTimestamp: timeRange.start, + endTimestamp: timeRange.end, + timeField: logsSource.timestampField, + messageField: logsSource.messageField, + documentFilters, + }, + } + ); + + const cancelOperation = useCallback(() => { + sendToCategorizeLogsService({ + type: 'cancel', + }); + }, [sendToCategorizeLogsService]); + + if (categorizeLogsServiceState.matches('done')) { + return ( + + ); + } else if (categorizeLogsServiceState.matches('failed')) { + return ; + } else if (categorizeLogsServiceState.matches('countingDocuments')) { + return ; + } else if ( + categorizeLogsServiceState.matches('fetchingSampledCategories') || + categorizeLogsServiceState.matches('fetchingRemainingCategories') + ) { + return ; + } else { + return null; + } +}; + +const consoleInspector = createConsoleInspector(); diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx new file mode 100644 index 0000000000000..4538b0ec2fd5d --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx @@ -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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import React from 'react'; +import type { IndexNameLogsSourceConfiguration } from '../../utils/logs_source'; +import { DiscoverLink } from '../discover_link'; + +export interface LogCategoriesControlBarProps { + documentFilters?: QueryDslQueryContainer[]; + logsSource: IndexNameLogsSourceConfiguration; + timeRange: { + start: string; + end: string; + }; + dependencies: LogCategoriesControlBarDependencies; +} + +export interface LogCategoriesControlBarDependencies { + share: SharePluginStart; +} + +export const LogCategoriesControlBar: React.FC = React.memo( + ({ dependencies, documentFilters, logsSource, timeRange }) => { + return ( + + + + + + ); + } +); diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_error_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_error_content.tsx new file mode 100644 index 0000000000000..1a335e3265294 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_error_content.tsx @@ -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 { EuiCodeBlock, EuiEmptyPrompt } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export interface LogCategoriesErrorContentProps { + error?: Error; +} + +export const LogCategoriesErrorContent: React.FC = ({ error }) => { + return ( + {logsOverviewErrorTitle}} + body={ + +

    {error?.stack ?? error?.toString() ?? unknownErrorDescription}

    +
    + } + layout="vertical" + /> + ); +}; + +const logsOverviewErrorTitle = i18n.translate( + 'xpack.observabilityLogsOverview.logCategories.errorTitle', + { + defaultMessage: 'Failed to categorize logs', + } +); + +const unknownErrorDescription = i18n.translate( + 'xpack.observabilityLogsOverview.logCategories.unknownErrorDescription', + { + defaultMessage: 'An unspecified error occurred.', + } +); diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx new file mode 100644 index 0000000000000..d9e960685de99 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx @@ -0,0 +1,182 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiDataGrid, + EuiDataGridColumnSortingConfig, + EuiDataGridPaginationProps, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { createConsoleInspector } from '@kbn/xstate-utils'; +import { useMachine } from '@xstate5/react'; +import _ from 'lodash'; +import React, { useMemo } from 'react'; +import { assign, setup } from 'xstate5'; +import { LogCategory } from '../../types'; +import { + LogCategoriesGridCellDependencies, + LogCategoriesGridColumnId, + createCellContext, + logCategoriesGridColumnIds, + logCategoriesGridColumns, + renderLogCategoriesGridCell, +} from './log_categories_grid_cell'; + +export interface LogCategoriesGridProps { + dependencies: LogCategoriesGridDependencies; + logCategories: LogCategory[]; +} + +export type LogCategoriesGridDependencies = LogCategoriesGridCellDependencies; + +export const LogCategoriesGrid: React.FC = ({ + dependencies, + logCategories, +}) => { + const [gridState, dispatchGridEvent] = useMachine(gridStateService, { + input: { + visibleColumns: logCategoriesGridColumns.map(({ id }) => id), + }, + inspect: consoleInspector, + }); + + const sortedLogCategories = useMemo(() => { + const sortingCriteria = gridState.context.sortingColumns.map( + ({ id, direction }): [(logCategory: LogCategory) => any, 'asc' | 'desc'] => { + switch (id) { + case 'count': + return [(logCategory: LogCategory) => logCategory.documentCount, direction]; + case 'change_type': + // TODO: use better sorting weight for change types + return [(logCategory: LogCategory) => logCategory.change.type, direction]; + case 'change_time': + return [ + (logCategory: LogCategory) => + 'timestamp' in logCategory.change ? logCategory.change.timestamp ?? '' : '', + direction, + ]; + default: + return [_.identity, direction]; + } + } + ); + return _.orderBy( + logCategories, + sortingCriteria.map(([accessor]) => accessor), + sortingCriteria.map(([, direction]) => direction) + ); + }, [gridState.context.sortingColumns, logCategories]); + + return ( + + dispatchGridEvent({ type: 'changeVisibleColumns', visibleColumns }), + }} + cellContext={createCellContext(sortedLogCategories, dependencies)} + pagination={{ + ...gridState.context.pagination, + onChangeItemsPerPage: (pageSize) => dispatchGridEvent({ type: 'changePageSize', pageSize }), + onChangePage: (pageIndex) => dispatchGridEvent({ type: 'changePageIndex', pageIndex }), + }} + renderCellValue={renderLogCategoriesGridCell} + rowCount={sortedLogCategories.length} + sorting={{ + columns: gridState.context.sortingColumns, + onSort: (sortingColumns) => + dispatchGridEvent({ type: 'changeSortingColumns', sortingColumns }), + }} + /> + ); +}; + +const gridStateService = setup({ + types: { + context: {} as { + visibleColumns: string[]; + pagination: Pick; + sortingColumns: LogCategoriesGridSortingConfig[]; + }, + events: {} as + | { + type: 'changePageSize'; + pageSize: number; + } + | { + type: 'changePageIndex'; + pageIndex: number; + } + | { + type: 'changeSortingColumns'; + sortingColumns: EuiDataGridColumnSortingConfig[]; + } + | { + type: 'changeVisibleColumns'; + visibleColumns: string[]; + }, + input: {} as { + visibleColumns: string[]; + }, + }, +}).createMachine({ + id: 'logCategoriesGridState', + context: ({ input }) => ({ + visibleColumns: input.visibleColumns, + pagination: { pageIndex: 0, pageSize: 20, pageSizeOptions: [10, 20, 50] }, + sortingColumns: [{ id: 'change_time', direction: 'desc' }], + }), + on: { + changePageSize: { + actions: assign(({ context, event }) => ({ + pagination: { + ...context.pagination, + pageIndex: 0, + pageSize: event.pageSize, + }, + })), + }, + changePageIndex: { + actions: assign(({ context, event }) => ({ + pagination: { + ...context.pagination, + pageIndex: event.pageIndex, + }, + })), + }, + changeSortingColumns: { + actions: assign(({ event }) => ({ + sortingColumns: event.sortingColumns.filter( + (sortingConfig): sortingConfig is LogCategoriesGridSortingConfig => + (logCategoriesGridColumnIds as string[]).includes(sortingConfig.id) + ), + })), + }, + changeVisibleColumns: { + actions: assign(({ event }) => ({ + visibleColumns: event.visibleColumns, + })), + }, + }, +}); + +const consoleInspector = createConsoleInspector(); + +const logCategoriesGridLabel = i18n.translate( + 'xpack.observabilityLogsOverview.logCategoriesGrid.euiDataGrid.logCategoriesLabel', + { defaultMessage: 'Log categories' } +); + +interface TypedEuiDataGridColumnSortingConfig + extends EuiDataGridColumnSortingConfig { + id: ColumnId; +} + +type LogCategoriesGridSortingConfig = + TypedEuiDataGridColumnSortingConfig; diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx new file mode 100644 index 0000000000000..d6ab4969eaf7b --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.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 { EuiDataGridColumn, RenderCellValue } from '@elastic/eui'; +import React from 'react'; +import { LogCategory } from '../../types'; +import { + LogCategoriesGridChangeTimeCell, + LogCategoriesGridChangeTimeCellDependencies, + logCategoriesGridChangeTimeColumn, +} from './log_categories_grid_change_time_cell'; +import { + LogCategoriesGridChangeTypeCell, + logCategoriesGridChangeTypeColumn, +} from './log_categories_grid_change_type_cell'; +import { + LogCategoriesGridCountCell, + logCategoriesGridCountColumn, +} from './log_categories_grid_count_cell'; +import { + LogCategoriesGridHistogramCell, + LogCategoriesGridHistogramCellDependencies, + logCategoriesGridHistoryColumn, +} from './log_categories_grid_histogram_cell'; +import { + LogCategoriesGridPatternCell, + logCategoriesGridPatternColumn, +} from './log_categories_grid_pattern_cell'; + +export interface LogCategoriesGridCellContext { + dependencies: LogCategoriesGridCellDependencies; + logCategories: LogCategory[]; +} + +export type LogCategoriesGridCellDependencies = LogCategoriesGridHistogramCellDependencies & + LogCategoriesGridChangeTimeCellDependencies; + +export const renderLogCategoriesGridCell: RenderCellValue = ({ + rowIndex, + columnId, + isExpanded, + ...rest +}) => { + const { dependencies, logCategories } = getCellContext(rest); + + const logCategory = logCategories[rowIndex]; + + switch (columnId as LogCategoriesGridColumnId) { + case 'pattern': + return ; + case 'count': + return ; + case 'history': + return ( + + ); + case 'change_type': + return ; + case 'change_time': + return ( + + ); + default: + return <>-; + } +}; + +export const logCategoriesGridColumns = [ + logCategoriesGridPatternColumn, + logCategoriesGridCountColumn, + logCategoriesGridChangeTypeColumn, + logCategoriesGridChangeTimeColumn, + logCategoriesGridHistoryColumn, +] satisfies EuiDataGridColumn[]; + +export const logCategoriesGridColumnIds = logCategoriesGridColumns.map(({ id }) => id); + +export type LogCategoriesGridColumnId = (typeof logCategoriesGridColumns)[number]['id']; + +const cellContextKey = 'cellContext'; + +const getCellContext = (cellContext: object): LogCategoriesGridCellContext => + (cellContextKey in cellContext + ? cellContext[cellContextKey] + : {}) as LogCategoriesGridCellContext; + +export const createCellContext = ( + logCategories: LogCategory[], + dependencies: LogCategoriesGridCellDependencies +): { [cellContextKey]: LogCategoriesGridCellContext } => ({ + [cellContextKey]: { + dependencies, + logCategories, + }, +}); diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_time_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_time_cell.tsx new file mode 100644 index 0000000000000..5ad8cbdd49346 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_time_cell.tsx @@ -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 { EuiDataGridColumn } from '@elastic/eui'; +import { SettingsStart } from '@kbn/core-ui-settings-browser'; +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import React, { useMemo } from 'react'; +import { LogCategory } from '../../types'; + +export const logCategoriesGridChangeTimeColumn = { + id: 'change_time' as const, + display: i18n.translate( + 'xpack.observabilityLogsOverview.logCategoriesGrid.changeTimeColumnLabel', + { + defaultMessage: 'Change at', + } + ), + isSortable: true, + initialWidth: 220, + schema: 'datetime', +} satisfies EuiDataGridColumn; + +export interface LogCategoriesGridChangeTimeCellProps { + dependencies: LogCategoriesGridChangeTimeCellDependencies; + logCategory: LogCategory; +} + +export interface LogCategoriesGridChangeTimeCellDependencies { + uiSettings: SettingsStart; +} + +export const LogCategoriesGridChangeTimeCell: React.FC = ({ + dependencies, + logCategory, +}) => { + const dateFormat = useMemo( + () => dependencies.uiSettings.client.get('dateFormat'), + [dependencies.uiSettings.client] + ); + if (!('timestamp' in logCategory.change && logCategory.change.timestamp != null)) { + return null; + } + + if (dateFormat) { + return <>{moment(logCategory.change.timestamp).format(dateFormat)}; + } else { + return <>{logCategory.change.timestamp}; + } +}; diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_type_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_type_cell.tsx new file mode 100644 index 0000000000000..af6349bd0e18c --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_type_cell.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiBadge, EuiDataGridColumn } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { LogCategory } from '../../types'; + +export const logCategoriesGridChangeTypeColumn = { + id: 'change_type' as const, + display: i18n.translate( + 'xpack.observabilityLogsOverview.logCategoriesGrid.changeTypeColumnLabel', + { + defaultMessage: 'Change type', + } + ), + isSortable: true, + initialWidth: 110, +} satisfies EuiDataGridColumn; + +export interface LogCategoriesGridChangeTypeCellProps { + logCategory: LogCategory; +} + +export const LogCategoriesGridChangeTypeCell: React.FC = ({ + logCategory, +}) => { + switch (logCategory.change.type) { + case 'dip': + return {dipBadgeLabel}; + case 'spike': + return {spikeBadgeLabel}; + case 'step': + return {stepBadgeLabel}; + case 'distribution': + return {distributionBadgeLabel}; + case 'rare': + return {rareBadgeLabel}; + case 'trend': + return {trendBadgeLabel}; + case 'other': + return {otherBadgeLabel}; + case 'none': + return <>-; + default: + return {unknownBadgeLabel}; + } +}; + +const dipBadgeLabel = i18n.translate( + 'xpack.observabilityLogsOverview.logCategories.dipChangeTypeBadgeLabel', + { + defaultMessage: 'Dip', + } +); + +const spikeBadgeLabel = i18n.translate( + 'xpack.observabilityLogsOverview.logCategories.spikeChangeTypeBadgeLabel', + { + defaultMessage: 'Spike', + } +); + +const stepBadgeLabel = i18n.translate( + 'xpack.observabilityLogsOverview.logCategories.spikeChangeTypeBadgeLabel', + { + defaultMessage: 'Step', + } +); + +const distributionBadgeLabel = i18n.translate( + 'xpack.observabilityLogsOverview.logCategories.distributionChangeTypeBadgeLabel', + { + defaultMessage: 'Distribution', + } +); + +const trendBadgeLabel = i18n.translate( + 'xpack.observabilityLogsOverview.logCategories.spikeChangeTypeBadgeLabel', + { + defaultMessage: 'Trend', + } +); + +const otherBadgeLabel = i18n.translate( + 'xpack.observabilityLogsOverview.logCategories.otherChangeTypeBadgeLabel', + { + defaultMessage: 'Other', + } +); + +const unknownBadgeLabel = i18n.translate( + 'xpack.observabilityLogsOverview.logCategories.unknownChangeTypeBadgeLabel', + { + defaultMessage: 'Unknown', + } +); + +const rareBadgeLabel = i18n.translate( + 'xpack.observabilityLogsOverview.logCategories.rareChangeTypeBadgeLabel', + { + defaultMessage: 'Rare', + } +); diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_count_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_count_cell.tsx new file mode 100644 index 0000000000000..f2247aab5212e --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_count_cell.tsx @@ -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 { EuiDataGridColumn } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedNumber } from '@kbn/i18n-react'; +import React from 'react'; +import { LogCategory } from '../../types'; + +export const logCategoriesGridCountColumn = { + id: 'count' as const, + display: i18n.translate('xpack.observabilityLogsOverview.logCategoriesGrid.countColumnLabel', { + defaultMessage: 'Events', + }), + isSortable: true, + schema: 'numeric', + initialWidth: 100, +} satisfies EuiDataGridColumn; + +export interface LogCategoriesGridCountCellProps { + logCategory: LogCategory; +} + +export const LogCategoriesGridCountCell: React.FC = ({ + logCategory, +}) => { + return ; +}; diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_histogram_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_histogram_cell.tsx new file mode 100644 index 0000000000000..2fb50b0f2f3b4 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_histogram_cell.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 { + BarSeries, + Chart, + LineAnnotation, + LineAnnotationStyle, + PartialTheme, + Settings, + Tooltip, + TooltipType, +} from '@elastic/charts'; +import { EuiDataGridColumn } from '@elastic/eui'; +import { ChartsPluginStart } from '@kbn/charts-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { RecursivePartial } from '@kbn/utility-types'; +import React from 'react'; +import { LogCategory, LogCategoryHistogramBucket } from '../../types'; + +export const logCategoriesGridHistoryColumn = { + id: 'history' as const, + display: i18n.translate( + 'xpack.observabilityLogsOverview.logCategoriesGrid.histogramColumnLabel', + { + defaultMessage: 'Timeline', + } + ), + isSortable: false, + initialWidth: 250, + isExpandable: false, +} satisfies EuiDataGridColumn; + +export interface LogCategoriesGridHistogramCellProps { + dependencies: LogCategoriesGridHistogramCellDependencies; + logCategory: LogCategory; +} + +export interface LogCategoriesGridHistogramCellDependencies { + charts: ChartsPluginStart; +} + +export const LogCategoriesGridHistogramCell: React.FC = ({ + dependencies: { charts }, + logCategory, +}) => { + const baseTheme = charts.theme.useChartsBaseTheme(); + const sparklineTheme = charts.theme.useSparklineOverrides(); + + return ( + + + + + {'timestamp' in logCategory.change && logCategory.change.timestamp != null ? ( + + ) : null} + + ); +}; + +const localThemeOverrides: PartialTheme = { + scales: { + histogramPadding: 0.1, + }, + background: { + color: 'transparent', + }, +}; + +const annotationStyle: RecursivePartial = { + line: { + strokeWidth: 2, + }, +}; + +const timestampAccessor = (histogram: LogCategoryHistogramBucket) => + new Date(histogram.timestamp).getTime(); diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx new file mode 100644 index 0000000000000..d507487a99e3c --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiDataGridColumn, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import React, { useMemo } from 'react'; +import { LogCategory } from '../../types'; + +export const logCategoriesGridPatternColumn = { + id: 'pattern' as const, + display: i18n.translate('xpack.observabilityLogsOverview.logCategoriesGrid.patternColumnLabel', { + defaultMessage: 'Pattern', + }), + isSortable: false, + schema: 'string', +} satisfies EuiDataGridColumn; + +export interface LogCategoriesGridPatternCellProps { + logCategory: LogCategory; +} + +export const LogCategoriesGridPatternCell: React.FC = ({ + logCategory, +}) => { + const theme = useEuiTheme(); + const { euiTheme } = theme; + const termsList = useMemo(() => logCategory.terms.split(' '), [logCategory.terms]); + + const commonStyle = css` + display: inline-block; + font-family: ${euiTheme.font.familyCode}; + margin-right: ${euiTheme.size.xs}; + `; + + const termStyle = css` + ${commonStyle}; + `; + + const separatorStyle = css` + ${commonStyle}; + color: ${euiTheme.colors.successText}; + `; + + return ( +
    +      
    *
    + {termsList.map((term, index) => ( + +
    {term}
    +
    *
    +
    + ))} +
    + ); +}; diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_loading_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_loading_content.tsx new file mode 100644 index 0000000000000..0fde469fe717d --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_loading_content.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +export interface LogCategoriesLoadingContentProps { + onCancel?: () => void; + stage: 'counting' | 'categorizing'; +} + +export const LogCategoriesLoadingContent: React.FC = ({ + onCancel, + stage, +}) => { + return ( + } + title={ +

    + {stage === 'counting' + ? logCategoriesLoadingStateCountingTitle + : logCategoriesLoadingStateCategorizingTitle} +

    + } + actions={ + onCancel != null + ? [ + { + onCancel(); + }} + > + {logCategoriesLoadingStateCancelButtonLabel} + , + ] + : [] + } + /> + ); +}; + +const logCategoriesLoadingStateCountingTitle = i18n.translate( + 'xpack.observabilityLogsOverview.logCategoriesGrid.loadingStageCountingTitle', + { + defaultMessage: 'Estimating log volume', + } +); + +const logCategoriesLoadingStateCategorizingTitle = i18n.translate( + 'xpack.observabilityLogsOverview.logCategoriesGrid.loadingStageCategorizingTitle', + { + defaultMessage: 'Categorizing logs', + } +); + +const logCategoriesLoadingStateCancelButtonLabel = i18n.translate( + 'xpack.observabilityLogsOverview.logCategoriesGrid.loadingStateCancelButtonLabel', + { + defaultMessage: 'Cancel', + } +); diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx new file mode 100644 index 0000000000000..e16bdda7cb44a --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { LogCategory } from '../../types'; +import { IndexNameLogsSourceConfiguration } from '../../utils/logs_source'; +import { + LogCategoriesControlBar, + LogCategoriesControlBarDependencies, +} from './log_categories_control_bar'; +import { LogCategoriesGrid, LogCategoriesGridDependencies } from './log_categories_grid'; + +export interface LogCategoriesResultContentProps { + dependencies: LogCategoriesResultContentDependencies; + documentFilters?: QueryDslQueryContainer[]; + logCategories: LogCategory[]; + logsSource: IndexNameLogsSourceConfiguration; + timeRange: { + start: string; + end: string; + }; +} + +export type LogCategoriesResultContentDependencies = LogCategoriesControlBarDependencies & + LogCategoriesGridDependencies; + +export const LogCategoriesResultContent: React.FC = ({ + dependencies, + documentFilters, + logCategories, + logsSource, + timeRange, +}) => { + if (logCategories.length === 0) { + return ; + } else { + return ( + + + + + + + + + ); + } +}; + +export const LogCategoriesEmptyResultContent: React.FC = () => { + return ( + {emptyResultContentDescription}

    } + color="subdued" + layout="horizontal" + title={

    {emptyResultContentTitle}

    } + titleSize="m" + /> + ); +}; + +const emptyResultContentTitle = i18n.translate( + 'xpack.observabilityLogsOverview.logCategories.emptyResultContentTitle', + { + defaultMessage: 'No log categories found', + } +); + +const emptyResultContentDescription = i18n.translate( + 'xpack.observabilityLogsOverview.logCategories.emptyResultContentDescription', + { + defaultMessage: + 'No suitable documents within the time range. Try searching for a longer time period.', + } +); diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/index.ts b/x-pack/packages/observability/logs_overview/src/components/logs_overview/index.ts new file mode 100644 index 0000000000000..878f634f078ad --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './logs_overview'; +export * from './logs_overview_error_content'; +export * from './logs_overview_loading_content'; diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx new file mode 100644 index 0000000000000..988656eb1571e --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { type LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; +import React from 'react'; +import useAsync from 'react-use/lib/useAsync'; +import { LogsSourceConfiguration, normalizeLogsSource } from '../../utils/logs_source'; +import { LogCategories, LogCategoriesDependencies } from '../log_categories'; +import { LogsOverviewErrorContent } from './logs_overview_error_content'; +import { LogsOverviewLoadingContent } from './logs_overview_loading_content'; + +export interface LogsOverviewProps { + dependencies: LogsOverviewDependencies; + documentFilters?: QueryDslQueryContainer[]; + logsSource?: LogsSourceConfiguration; + timeRange: { + start: string; + end: string; + }; +} + +export type LogsOverviewDependencies = LogCategoriesDependencies & { + logsDataAccess: LogsDataAccessPluginStart; +}; + +export const LogsOverview: React.FC = React.memo( + ({ + dependencies, + documentFilters = defaultDocumentFilters, + logsSource = defaultLogsSource, + timeRange, + }) => { + const normalizedLogsSource = useAsync( + () => normalizeLogsSource({ logsDataAccess: dependencies.logsDataAccess })(logsSource), + [dependencies.logsDataAccess, logsSource] + ); + + if (normalizedLogsSource.loading) { + return ; + } + + if (normalizedLogsSource.error != null || normalizedLogsSource.value == null) { + return ; + } + + return ( + + ); + } +); + +const defaultDocumentFilters: QueryDslQueryContainer[] = []; + +const defaultLogsSource: LogsSourceConfiguration = { type: 'shared_setting' }; diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.tsx b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.tsx new file mode 100644 index 0000000000000..73586756bb908 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.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 { EuiCodeBlock, EuiEmptyPrompt } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export interface LogsOverviewErrorContentProps { + error?: Error; +} + +export const LogsOverviewErrorContent: React.FC = ({ error }) => { + return ( + {logsOverviewErrorTitle}} + body={ + +

    {error?.stack ?? error?.toString() ?? unknownErrorDescription}

    +
    + } + layout="vertical" + /> + ); +}; + +const logsOverviewErrorTitle = i18n.translate('xpack.observabilityLogsOverview.errorTitle', { + defaultMessage: 'Error', +}); + +const unknownErrorDescription = i18n.translate( + 'xpack.observabilityLogsOverview.unknownErrorDescription', + { + defaultMessage: 'An unspecified error occurred.', + } +); diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_loading_content.tsx b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_loading_content.tsx new file mode 100644 index 0000000000000..7645fdb90f0ac --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_loading_content.tsx @@ -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 { EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export const LogsOverviewLoadingContent: React.FC = ({}) => { + return ( + } + title={

    {logsOverviewLoadingTitle}

    } + /> + ); +}; + +const logsOverviewLoadingTitle = i18n.translate('xpack.observabilityLogsOverview.loadingTitle', { + defaultMessage: 'Loading', +}); diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts new file mode 100644 index 0000000000000..7260efe63d435 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts @@ -0,0 +1,282 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ISearchGeneric } from '@kbn/search-types'; +import { lastValueFrom } from 'rxjs'; +import { fromPromise } from 'xstate5'; +import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; +import { z } from '@kbn/zod'; +import { LogCategorizationParams } from './types'; +import { createCategorizationRequestParams } from './queries'; +import { LogCategory, LogCategoryChange } from '../../types'; + +// the fraction of a category's histogram below which the category is considered rare +const rarityThreshold = 0.2; +const maxCategoriesCount = 1000; + +export const categorizeDocuments = ({ search }: { search: ISearchGeneric }) => + fromPromise< + { + categories: LogCategory[]; + hasReachedLimit: boolean; + }, + LogCategorizationParams & { + samplingProbability: number; + ignoredCategoryTerms: string[]; + minDocsPerCategory: number; + } + >( + async ({ + input: { + index, + endTimestamp, + startTimestamp, + timeField, + messageField, + samplingProbability, + ignoredCategoryTerms, + documentFilters = [], + minDocsPerCategory, + }, + signal, + }) => { + const randomSampler = createRandomSamplerWrapper({ + probability: samplingProbability, + seed: 1, + }); + + const requestParams = createCategorizationRequestParams({ + index, + timeField, + messageField, + startTimestamp, + endTimestamp, + randomSampler, + additionalFilters: documentFilters, + ignoredCategoryTerms, + minDocsPerCategory, + maxCategoriesCount, + }); + + const { rawResponse } = await lastValueFrom( + search({ params: requestParams }, { abortSignal: signal }) + ); + + if (rawResponse.aggregations == null) { + throw new Error('No aggregations found in large categories response'); + } + + const logCategoriesAggResult = randomSampler.unwrap(rawResponse.aggregations); + + if (!('categories' in logCategoriesAggResult)) { + throw new Error('No categorization aggregation found in large categories response'); + } + + const logCategories = + (logCategoriesAggResult.categories.buckets as unknown[]).map(mapCategoryBucket) ?? []; + + return { + categories: logCategories, + hasReachedLimit: logCategories.length >= maxCategoriesCount, + }; + } + ); + +const mapCategoryBucket = (bucket: any): LogCategory => + esCategoryBucketSchema + .transform((parsedBucket) => ({ + change: mapChangePoint(parsedBucket), + documentCount: parsedBucket.doc_count, + histogram: parsedBucket.histogram, + terms: parsedBucket.key, + })) + .parse(bucket); + +const mapChangePoint = ({ change, histogram }: EsCategoryBucket): LogCategoryChange => { + switch (change.type) { + case 'stationary': + if (isRareInHistogram(histogram)) { + return { + type: 'rare', + timestamp: findFirstNonZeroBucket(histogram)?.timestamp ?? histogram[0].timestamp, + }; + } else { + return { + type: 'none', + }; + } + case 'dip': + case 'spike': + return { + type: change.type, + timestamp: change.bucket.key, + }; + case 'step_change': + return { + type: 'step', + timestamp: change.bucket.key, + }; + case 'distribution_change': + return { + type: 'distribution', + timestamp: change.bucket.key, + }; + case 'trend_change': + return { + type: 'trend', + timestamp: change.bucket.key, + correlationCoefficient: change.details.r_value, + }; + case 'unknown': + return { + type: 'unknown', + rawChange: change.rawChange, + }; + case 'non_stationary': + default: + return { + type: 'other', + }; + } +}; + +/** + * The official types are lacking the change_point aggregation + */ +const esChangePointBucketSchema = z.object({ + key: z.string().datetime(), + doc_count: z.number(), +}); + +const esChangePointDetailsSchema = z.object({ + p_value: z.number(), +}); + +const esChangePointCorrelationSchema = esChangePointDetailsSchema.extend({ + r_value: z.number(), +}); + +const esChangePointSchema = z.union([ + z + .object({ + bucket: esChangePointBucketSchema, + type: z.object({ + dip: esChangePointDetailsSchema, + }), + }) + .transform(({ bucket, type: { dip: details } }) => ({ + type: 'dip' as const, + bucket, + details, + })), + z + .object({ + bucket: esChangePointBucketSchema, + type: z.object({ + spike: esChangePointDetailsSchema, + }), + }) + .transform(({ bucket, type: { spike: details } }) => ({ + type: 'spike' as const, + bucket, + details, + })), + z + .object({ + bucket: esChangePointBucketSchema, + type: z.object({ + step_change: esChangePointDetailsSchema, + }), + }) + .transform(({ bucket, type: { step_change: details } }) => ({ + type: 'step_change' as const, + bucket, + details, + })), + z + .object({ + bucket: esChangePointBucketSchema, + type: z.object({ + trend_change: esChangePointCorrelationSchema, + }), + }) + .transform(({ bucket, type: { trend_change: details } }) => ({ + type: 'trend_change' as const, + bucket, + details, + })), + z + .object({ + bucket: esChangePointBucketSchema, + type: z.object({ + distribution_change: esChangePointDetailsSchema, + }), + }) + .transform(({ bucket, type: { distribution_change: details } }) => ({ + type: 'distribution_change' as const, + bucket, + details, + })), + z + .object({ + type: z.object({ + non_stationary: esChangePointCorrelationSchema.extend({ + trend: z.enum(['increasing', 'decreasing']), + }), + }), + }) + .transform(({ type: { non_stationary: details } }) => ({ + type: 'non_stationary' as const, + details, + })), + z + .object({ + type: z.object({ + stationary: z.object({}), + }), + }) + .transform(() => ({ type: 'stationary' as const })), + z + .object({ + type: z.object({}), + }) + .transform((value) => ({ type: 'unknown' as const, rawChange: JSON.stringify(value) })), +]); + +const esHistogramSchema = z + .object({ + buckets: z.array( + z + .object({ + key_as_string: z.string(), + doc_count: z.number(), + }) + .transform((bucket) => ({ + timestamp: bucket.key_as_string, + documentCount: bucket.doc_count, + })) + ), + }) + .transform(({ buckets }) => buckets); + +type EsHistogram = z.output; + +const esCategoryBucketSchema = z.object({ + key: z.string(), + doc_count: z.number(), + change: esChangePointSchema, + histogram: esHistogramSchema, +}); + +type EsCategoryBucket = z.output; + +const isRareInHistogram = (histogram: EsHistogram): boolean => + histogram.filter((bucket) => bucket.documentCount > 0).length < + histogram.length * rarityThreshold; + +const findFirstNonZeroBucket = (histogram: EsHistogram) => + histogram.find((bucket) => bucket.documentCount > 0); diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts new file mode 100644 index 0000000000000..deeb758d2d737 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts @@ -0,0 +1,250 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MachineImplementationsFrom, assign, setup } from 'xstate5'; +import { LogCategory } from '../../types'; +import { getPlaceholderFor } from '../../utils/xstate5_utils'; +import { categorizeDocuments } from './categorize_documents'; +import { countDocuments } from './count_documents'; +import { CategorizeLogsServiceDependencies, LogCategorizationParams } from './types'; + +export const categorizeLogsService = setup({ + types: { + input: {} as LogCategorizationParams, + output: {} as { + categories: LogCategory[]; + documentCount: number; + hasReachedLimit: boolean; + samplingProbability: number; + }, + context: {} as { + categories: LogCategory[]; + documentCount: number; + error?: Error; + hasReachedLimit: boolean; + parameters: LogCategorizationParams; + samplingProbability: number; + }, + events: {} as { + type: 'cancel'; + }, + }, + actors: { + countDocuments: getPlaceholderFor(countDocuments), + categorizeDocuments: getPlaceholderFor(categorizeDocuments), + }, + actions: { + storeError: assign((_, params: { error: unknown }) => ({ + error: params.error instanceof Error ? params.error : new Error(String(params.error)), + })), + storeCategories: assign( + ({ context }, params: { categories: LogCategory[]; hasReachedLimit: boolean }) => ({ + categories: [...context.categories, ...params.categories], + hasReachedLimit: params.hasReachedLimit, + }) + ), + storeDocumentCount: assign( + (_, params: { documentCount: number; samplingProbability: number }) => ({ + documentCount: params.documentCount, + samplingProbability: params.samplingProbability, + }) + ), + }, + guards: { + hasTooFewDocuments: (_guardArgs, params: { documentCount: number }) => params.documentCount < 1, + requiresSampling: (_guardArgs, params: { samplingProbability: number }) => + params.samplingProbability < 1, + }, +}).createMachine({ + /** @xstate-layout N4IgpgJg5mDOIC5QGMCGAXMUD2AnAlgF5gAy2UsAdMtgK4B26+9UAItsrQLZiOwDEEbPTCVmAN2wBrUWkw4CxMhWp1GzNh2690sBBI4Z8wgNoAGALrmLiUAAdssfE2G2QAD0QBmMwA5KACy+AQFmob4AjABMwQBsADQgAJ6IkYEAnJkA7FmxZlERmQGxAL4liXJYeESk5FQ0DEws7Jw8fILCogYy1BhVirUqDerNWm26+vSScsb01iYRNkggDk4u9G6eCD7+QSFhftFxiSkIvgCsWZSxEVlRsbFZ52Zm515lFX0KNcr1ak2aVo6ARCERiKbSWRfapKOqqRoaFraPiTaZGUyWExRJb2RzOWabbx+QLBULhI7FE7eWL+F45GnRPIRZkfECVb6wob-RFjYH8MC4XB4Sh2AA2GAAZnguL15DDBn8EaMgSiDDMMVZLG5VvjXMstjsSftyTFKclEOdzgFKF5zukvA8zBFnl50udWez5b94SNAcjdPw0PRkGBRdZtXj1oTtsS9mTDqaEuaEBF8udKFkIr5fK6olkzOksgEPdCBt6JWB0MgABYaADKqC4YsgAGFS-g4B0wd0oXKBg2m6LW+24OHljqo-rEMzbpQos8-K7fC9CknTrF0rEbbb0oVMoWIgF3eU2e3OVQK1XaywB82IG2+x2BAKhbgReL0FLcDLPf3G3eH36J8x1xNYCSnFNmSuecXhzdJlydTcqQQLJfHSOc0PyLJN3SMxYiPEtH3PShLxret-yHe8RwEIMQzDLVx0jcDQC2GdoIXOCENXZDsyiOcAiiKJ0iiPDLi8V1CKA4jSOvKAACUwC4VBmA0QDvk7UEughHpfxqBSlJUlg1OqUcGNA3UNggrMs347IjzdaIvGQwSvECXI8k3Z43gEiJJI5BUSMrMiWH05T6FU6j+UFYUxUlaVZSksBQsMqBjIIUycRWJi9RY6dIn8KIAjsu1zkc5CAmiG1fBiaIzB8B0QmPT4iICmSNGS8KjMi2jQxArKwJyjw8pswriocqInOTLwIi3ASD1yQpswCd5WXobAIDgNxdPPCMBss3KEAAWjXRBDvTfcLsu9Jlr8r04WGAEkXGeBGL26MBOQzIt2ut4cwmirCt8W6yzhNqbwo4dH0216LOjTMIjnBdYhK1DYgdHjihtZbUIdWIXJuYGflBoLZI6iKoZe8zJwOw9KtGt1kbuTcsmQrwi0oeCQjzZ5blwt1Cek5TKN22GIIKZbAgKC45pyLyeLwtz4Kyabs1QgWAs0kXqaGhBxdcnzpaE2XXmch0MORmaBJeLwjbKMogA */ + id: 'categorizeLogs', + context: ({ input }) => ({ + categories: [], + documentCount: 0, + hasReachedLimit: false, + parameters: input, + samplingProbability: 1, + }), + initial: 'countingDocuments', + states: { + countingDocuments: { + invoke: { + src: 'countDocuments', + input: ({ context }) => context.parameters, + onDone: [ + { + target: 'done', + guard: { + type: 'hasTooFewDocuments', + params: ({ event }) => event.output, + }, + actions: [ + { + type: 'storeDocumentCount', + params: ({ event }) => event.output, + }, + ], + }, + { + target: 'fetchingSampledCategories', + guard: { + type: 'requiresSampling', + params: ({ event }) => event.output, + }, + actions: [ + { + type: 'storeDocumentCount', + params: ({ event }) => event.output, + }, + ], + }, + { + target: 'fetchingRemainingCategories', + actions: [ + { + type: 'storeDocumentCount', + params: ({ event }) => event.output, + }, + ], + }, + ], + onError: { + target: 'failed', + actions: [ + { + type: 'storeError', + params: ({ event }) => ({ error: event.error }), + }, + ], + }, + }, + + on: { + cancel: { + target: 'failed', + actions: [ + { + type: 'storeError', + params: () => ({ error: new Error('Counting cancelled') }), + }, + ], + }, + }, + }, + + fetchingSampledCategories: { + invoke: { + src: 'categorizeDocuments', + id: 'categorizeSampledCategories', + input: ({ context }) => ({ + ...context.parameters, + samplingProbability: context.samplingProbability, + ignoredCategoryTerms: [], + minDocsPerCategory: 10, + }), + onDone: { + target: 'fetchingRemainingCategories', + actions: [ + { + type: 'storeCategories', + params: ({ event }) => event.output, + }, + ], + }, + onError: { + target: 'failed', + actions: [ + { + type: 'storeError', + params: ({ event }) => ({ error: event.error }), + }, + ], + }, + }, + + on: { + cancel: { + target: 'failed', + actions: [ + { + type: 'storeError', + params: () => ({ error: new Error('Categorization cancelled') }), + }, + ], + }, + }, + }, + + fetchingRemainingCategories: { + invoke: { + src: 'categorizeDocuments', + id: 'categorizeRemainingCategories', + input: ({ context }) => ({ + ...context.parameters, + samplingProbability: 1, + ignoredCategoryTerms: context.categories.map((category) => category.terms), + minDocsPerCategory: 0, + }), + onDone: { + target: 'done', + actions: [ + { + type: 'storeCategories', + params: ({ event }) => event.output, + }, + ], + }, + onError: { + target: 'failed', + actions: [ + { + type: 'storeError', + params: ({ event }) => ({ error: event.error }), + }, + ], + }, + }, + + on: { + cancel: { + target: 'failed', + actions: [ + { + type: 'storeError', + params: () => ({ error: new Error('Categorization cancelled') }), + }, + ], + }, + }, + }, + + failed: { + type: 'final', + }, + + done: { + type: 'final', + }, + }, + output: ({ context }) => ({ + categories: context.categories, + documentCount: context.documentCount, + hasReachedLimit: context.hasReachedLimit, + samplingProbability: context.samplingProbability, + }), +}); + +export const createCategorizeLogsServiceImplementations = ({ + search, +}: CategorizeLogsServiceDependencies): MachineImplementationsFrom< + typeof categorizeLogsService +> => ({ + actors: { + categorizeDocuments: categorizeDocuments({ search }), + countDocuments: countDocuments({ search }), + }, +}); diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts new file mode 100644 index 0000000000000..359f9ddac2bd8 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getSampleProbability } from '@kbn/ml-random-sampler-utils'; +import { ISearchGeneric } from '@kbn/search-types'; +import { lastValueFrom } from 'rxjs'; +import { fromPromise } from 'xstate5'; +import { LogCategorizationParams } from './types'; +import { createCategorizationQuery } from './queries'; + +export const countDocuments = ({ search }: { search: ISearchGeneric }) => + fromPromise< + { + documentCount: number; + samplingProbability: number; + }, + LogCategorizationParams + >( + async ({ + input: { index, endTimestamp, startTimestamp, timeField, messageField, documentFilters }, + signal, + }) => { + const { rawResponse: totalHitsResponse } = await lastValueFrom( + search( + { + params: { + index, + size: 0, + track_total_hits: true, + query: createCategorizationQuery({ + messageField, + timeField, + startTimestamp, + endTimestamp, + additionalFilters: documentFilters, + }), + }, + }, + { abortSignal: signal } + ) + ); + + const documentCount = + totalHitsResponse.hits.total == null + ? 0 + : typeof totalHitsResponse.hits.total === 'number' + ? totalHitsResponse.hits.total + : totalHitsResponse.hits.total.value; + const samplingProbability = getSampleProbability(documentCount); + + return { + documentCount, + samplingProbability, + }; + } + ); diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/index.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/index.ts new file mode 100644 index 0000000000000..149359b7d2015 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_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 * from './categorize_logs_service'; diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts new file mode 100644 index 0000000000000..aef12da303bcc --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { calculateAuto } from '@kbn/calculate-auto'; +import { RandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; +import moment from 'moment'; + +const isoTimestampFormat = "YYYY-MM-DD'T'HH:mm:ss.SSS'Z'"; + +export const createCategorizationQuery = ({ + messageField, + timeField, + startTimestamp, + endTimestamp, + additionalFilters = [], + ignoredCategoryTerms = [], +}: { + messageField: string; + timeField: string; + startTimestamp: string; + endTimestamp: string; + additionalFilters?: QueryDslQueryContainer[]; + ignoredCategoryTerms?: string[]; +}): QueryDslQueryContainer => { + return { + bool: { + filter: [ + { + exists: { + field: messageField, + }, + }, + { + range: { + [timeField]: { + gte: startTimestamp, + lte: endTimestamp, + format: 'strict_date_time', + }, + }, + }, + ...additionalFilters, + ], + must_not: ignoredCategoryTerms.map(createCategoryQuery(messageField)), + }, + }; +}; + +export const createCategorizationRequestParams = ({ + index, + timeField, + messageField, + startTimestamp, + endTimestamp, + randomSampler, + minDocsPerCategory = 0, + additionalFilters = [], + ignoredCategoryTerms = [], + maxCategoriesCount = 1000, +}: { + startTimestamp: string; + endTimestamp: string; + index: string; + timeField: string; + messageField: string; + randomSampler: RandomSamplerWrapper; + minDocsPerCategory?: number; + additionalFilters?: QueryDslQueryContainer[]; + ignoredCategoryTerms?: string[]; + maxCategoriesCount?: number; +}) => { + const startMoment = moment(startTimestamp, isoTimestampFormat); + const endMoment = moment(endTimestamp, isoTimestampFormat); + const fixedIntervalDuration = calculateAuto.atLeast( + 24, + moment.duration(endMoment.diff(startMoment)) + ); + const fixedIntervalSize = `${Math.ceil(fixedIntervalDuration?.asMinutes() ?? 1)}m`; + + return { + index, + size: 0, + track_total_hits: false, + query: createCategorizationQuery({ + messageField, + timeField, + startTimestamp, + endTimestamp, + additionalFilters, + ignoredCategoryTerms, + }), + aggs: randomSampler.wrap({ + histogram: { + date_histogram: { + field: timeField, + fixed_interval: fixedIntervalSize, + extended_bounds: { + min: startTimestamp, + max: endTimestamp, + }, + }, + }, + categories: { + categorize_text: { + field: messageField, + size: maxCategoriesCount, + categorization_analyzer: { + tokenizer: 'standard', + }, + ...(minDocsPerCategory > 0 ? { min_doc_count: minDocsPerCategory } : {}), + }, + aggs: { + histogram: { + date_histogram: { + field: timeField, + fixed_interval: fixedIntervalSize, + extended_bounds: { + min: startTimestamp, + max: endTimestamp, + }, + }, + }, + change: { + // @ts-expect-error the official types don't support the change_point aggregation + change_point: { + buckets_path: 'histogram>_count', + }, + }, + }, + }, + }), + }; +}; + +export const createCategoryQuery = + (messageField: string) => + (categoryTerms: string): QueryDslQueryContainer => ({ + match: { + [messageField]: { + query: categoryTerms, + operator: 'AND' as const, + fuzziness: 0, + auto_generate_synonyms_phrase_query: false, + }, + }, + }); diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts new file mode 100644 index 0000000000000..e094317a98d62 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { ISearchGeneric } from '@kbn/search-types'; + +export interface CategorizeLogsServiceDependencies { + search: ISearchGeneric; +} + +export interface LogCategorizationParams { + documentFilters: QueryDslQueryContainer[]; + endTimestamp: string; + index: string; + messageField: string; + startTimestamp: string; + timeField: string; +} diff --git a/x-pack/packages/observability/logs_overview/src/types.ts b/x-pack/packages/observability/logs_overview/src/types.ts new file mode 100644 index 0000000000000..4c3d27eca7e7c --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/types.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface LogCategory { + change: LogCategoryChange; + documentCount: number; + histogram: LogCategoryHistogramBucket[]; + terms: string; +} + +export type LogCategoryChange = + | LogCategoryNoChange + | LogCategoryRareChange + | LogCategorySpikeChange + | LogCategoryDipChange + | LogCategoryStepChange + | LogCategoryDistributionChange + | LogCategoryTrendChange + | LogCategoryOtherChange + | LogCategoryUnknownChange; + +export interface LogCategoryNoChange { + type: 'none'; +} + +export interface LogCategoryRareChange { + type: 'rare'; + timestamp: string; +} + +export interface LogCategorySpikeChange { + type: 'spike'; + timestamp: string; +} + +export interface LogCategoryDipChange { + type: 'dip'; + timestamp: string; +} + +export interface LogCategoryStepChange { + type: 'step'; + timestamp: string; +} + +export interface LogCategoryTrendChange { + type: 'trend'; + timestamp: string; + correlationCoefficient: number; +} + +export interface LogCategoryDistributionChange { + type: 'distribution'; + timestamp: string; +} + +export interface LogCategoryOtherChange { + type: 'other'; + timestamp?: string; +} + +export interface LogCategoryUnknownChange { + type: 'unknown'; + rawChange: string; +} + +export interface LogCategoryHistogramBucket { + documentCount: number; + timestamp: string; +} diff --git a/x-pack/packages/observability/logs_overview/src/utils/logs_source.ts b/x-pack/packages/observability/logs_overview/src/utils/logs_source.ts new file mode 100644 index 0000000000000..0c8767c8702d4 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/utils/logs_source.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { type AbstractDataView } from '@kbn/data-views-plugin/common'; +import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; + +export type LogsSourceConfiguration = + | SharedSettingLogsSourceConfiguration + | IndexNameLogsSourceConfiguration + | DataViewLogsSourceConfiguration; + +export interface SharedSettingLogsSourceConfiguration { + type: 'shared_setting'; + timestampField?: string; + messageField?: string; +} + +export interface IndexNameLogsSourceConfiguration { + type: 'index_name'; + indexName: string; + timestampField: string; + messageField: string; +} + +export interface DataViewLogsSourceConfiguration { + type: 'data_view'; + dataView: AbstractDataView; + messageField?: string; +} + +export const normalizeLogsSource = + ({ logsDataAccess }: { logsDataAccess: LogsDataAccessPluginStart }) => + async (logsSource: LogsSourceConfiguration): Promise => { + switch (logsSource.type) { + case 'index_name': + return logsSource; + case 'shared_setting': + const logSourcesFromSharedSettings = + await logsDataAccess.services.logSourcesService.getLogSources(); + return { + type: 'index_name', + indexName: logSourcesFromSharedSettings + .map((logSource) => logSource.indexPattern) + .join(','), + timestampField: logsSource.timestampField ?? '@timestamp', + messageField: logsSource.messageField ?? 'message', + }; + case 'data_view': + return { + type: 'index_name', + indexName: logsSource.dataView.getIndexPattern(), + timestampField: logsSource.dataView.timeFieldName ?? '@timestamp', + messageField: logsSource.messageField ?? 'message', + }; + } + }; diff --git a/x-pack/packages/observability/logs_overview/src/utils/xstate5_utils.ts b/x-pack/packages/observability/logs_overview/src/utils/xstate5_utils.ts new file mode 100644 index 0000000000000..3df0bf4ea3988 --- /dev/null +++ b/x-pack/packages/observability/logs_overview/src/utils/xstate5_utils.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const getPlaceholderFor = any>( + implementationFactory: ImplementationFactory +): ReturnType => + (() => { + throw new Error('Not implemented'); + }) as ReturnType; diff --git a/x-pack/packages/observability/logs_overview/tsconfig.json b/x-pack/packages/observability/logs_overview/tsconfig.json new file mode 100644 index 0000000000000..886062ae8855f --- /dev/null +++ b/x-pack/packages/observability/logs_overview/tsconfig.json @@ -0,0 +1,39 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + "@kbn/ambient-ui-types", + "@kbn/ambient-storybook-types", + "@emotion/react/types/css-prop" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/data-views-plugin", + "@kbn/i18n", + "@kbn/search-types", + "@kbn/xstate-utils", + "@kbn/core-ui-settings-browser", + "@kbn/i18n-react", + "@kbn/charts-plugin", + "@kbn/utility-types", + "@kbn/logs-data-access-plugin", + "@kbn/ml-random-sampler-utils", + "@kbn/zod", + "@kbn/calculate-auto", + "@kbn/discover-plugin", + "@kbn/es-query", + "@kbn/router-utils", + "@kbn/share-plugin", + ] +} diff --git a/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.stories.tsx b/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.stories.tsx index 90b6887636c8a..c1b292c3f08cc 100644 --- a/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.stories.tsx +++ b/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.stories.tsx @@ -70,6 +70,14 @@ export const DistributionBar = () => { , + + +

    {'Hide last tooltip'}

    +
    + + + +
    ,

    {'Empty state'}

    diff --git a/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.test.tsx b/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.test.tsx index d4bdf4c20f133..e83b66e5e01e7 100644 --- a/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.test.tsx +++ b/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.test.tsx @@ -79,5 +79,67 @@ describe('DistributionBar', () => { }); }); + it('should render last tooltip by default', () => { + const stats = [ + { + key: 'low', + count: 9, + color: 'green', + }, + { + key: 'medium', + count: 90, + color: 'red', + }, + { + key: 'high', + count: 900, + color: 'red', + }, + ]; + + const { container } = render( + + ); + expect(container).toBeInTheDocument(); + const parts = container.querySelectorAll(`[classname*="distribution_bar--tooltip"]`); + parts.forEach((part, index) => { + if (index < parts.length - 1) { + expect(part).toHaveStyle({ opacity: 0 }); + } else { + expect(part).toHaveStyle({ opacity: 1 }); + } + }); + }); + + it('should not render last tooltip when hideLastTooltip is true', () => { + const stats = [ + { + key: 'low', + count: 9, + color: 'green', + }, + { + key: 'medium', + count: 90, + color: 'red', + }, + { + key: 'high', + count: 900, + color: 'red', + }, + ]; + + const { container } = render( + + ); + expect(container).toBeInTheDocument(); + const parts = container.querySelectorAll(`[classname*="distribution_bar--tooltip"]`); + parts.forEach((part) => { + expect(part).toHaveStyle({ opacity: 0 }); + }); + }); + // todo: test tooltip visibility logic }); diff --git a/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.tsx b/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.tsx index 28d8ca4a8a148..5b06292813ccd 100644 --- a/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.tsx +++ b/x-pack/packages/security-solution/distribution_bar/src/distribution_bar.tsx @@ -13,6 +13,8 @@ import { css } from '@emotion/react'; export interface DistributionBarProps { /** distribution data points */ stats: Array<{ key: string; count: number; color: string; label?: React.ReactNode }>; + /** hide the label above the bar at first render */ + hideLastTooltip?: boolean; /** data-test-subj used for querying the component in tests */ ['data-test-subj']?: string; } @@ -136,18 +138,21 @@ export const DistributionBar: React.FC = React.memo(functi props ) { const styles = useStyles(); - const { stats, 'data-test-subj': dataTestSubj } = props; + const { stats, 'data-test-subj': dataTestSubj, hideLastTooltip } = props; const parts = stats.map((stat) => { const partStyle = [ styles.part.base, styles.part.tick, styles.part.hover, - styles.part.lastTooltip, css` background-color: ${stat.color}; flex: ${stat.count}; `, ]; + if (!hideLastTooltip) { + partStyle.push(styles.part.lastTooltip); + } + const prettyNumber = numeral(stat.count).format('0,0a'); return ( diff --git a/x-pack/packages/security/api_key_management/src/components/api_key_badge.tsx b/x-pack/packages/security/api_key_management/src/components/api_key_badge.tsx index d8fb2822a1e96..c0c1c2a1e823d 100644 --- a/x-pack/packages/security/api_key_management/src/components/api_key_badge.tsx +++ b/x-pack/packages/security/api_key_management/src/components/api_key_badge.tsx @@ -5,8 +5,10 @@ * 2.0. */ -import { EuiToolTip, EuiBadge } from '@elastic/eui'; -import React, { FunctionComponent } from 'react'; +import { EuiBadge, EuiToolTip } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; + import { FormattedMessage } from '@kbn/i18n-react'; export interface ApiKeyBadgeProps { diff --git a/x-pack/packages/security/api_key_management/src/components/api_key_created_callout.tsx b/x-pack/packages/security/api_key_management/src/components/api_key_created_callout.tsx index 1f2f0201d5a48..daea08b51bf96 100644 --- a/x-pack/packages/security/api_key_management/src/components/api_key_created_callout.tsx +++ b/x-pack/packages/security/api_key_management/src/components/api_key_created_callout.tsx @@ -6,10 +6,13 @@ */ import { EuiCallOut } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; + import { i18n } from '@kbn/i18n'; -import React, { FunctionComponent } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { CreateAPIKeyResult } from './api_keys_api_client'; + +import type { CreateAPIKeyResult } from './api_keys_api_client'; import { SelectableTokenField } from './token_field'; export interface ApiKeyCreatedCalloutProps { diff --git a/x-pack/packages/security/api_key_management/src/components/api_key_flyout.tsx b/x-pack/packages/security/api_key_management/src/components/api_key_flyout.tsx index 4a8fa74095957..1edaca0761c47 100644 --- a/x-pack/packages/security/api_key_management/src/components/api_key_flyout.tsx +++ b/x-pack/packages/security/api_key_management/src/components/api_key_flyout.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { ExclusiveUnion, htmlIdGenerator } from '@elastic/eui'; +import type { ExclusiveUnion } from '@elastic/eui'; import { EuiButton, EuiButtonEmpty, @@ -27,12 +27,13 @@ import { EuiSwitch, EuiText, EuiTitle, + htmlIdGenerator, useEuiTheme, } from '@elastic/eui'; import { Form, FormikProvider, useFormik } from 'formik'; import moment from 'moment-timezone'; -import { FunctionComponent, useRef } from 'react'; -import React, { useEffect, useState } from 'react'; +import type { FunctionComponent } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import useAsyncFn from 'react-use/lib/useAsyncFn'; import { CodeEditorField } from '@kbn/code-editor'; @@ -41,10 +42,13 @@ import { i18n } from '@kbn/i18n'; import { FormattedDate, FormattedMessage } from '@kbn/i18n-react'; import { useDarkMode, useKibana } from '@kbn/kibana-react-plugin/public'; import type { KibanaServerError } from '@kbn/kibana-utils-plugin/public'; - -import { Role } from '@kbn/security-plugin-types-common'; import { FormField, FormRow } from '@kbn/security-form-components'; -import type { ApiKeyRoleDescriptors, CategorizedApiKey } from '@kbn/security-plugin-types-common'; +import type { + ApiKeyRoleDescriptors, + CategorizedApiKey, + Role, +} from '@kbn/security-plugin-types-common'; + import { ApiKeyBadge, ApiKeyStatus, TimeToolTip } from '.'; import { APIKeysAPIClient } from './api_keys_api_client'; import type { diff --git a/x-pack/packages/security/api_key_management/src/components/api_key_status.tsx b/x-pack/packages/security/api_key_management/src/components/api_key_status.tsx index 677544866e3cf..c45ad145d9e51 100644 --- a/x-pack/packages/security/api_key_management/src/components/api_key_status.tsx +++ b/x-pack/packages/security/api_key_management/src/components/api_key_status.tsx @@ -7,9 +7,12 @@ import { EuiHealth } from '@elastic/eui'; import moment from 'moment'; -import React, { FunctionComponent } from 'react'; +import type { FunctionComponent } from 'react'; +import React from 'react'; + import { FormattedMessage } from '@kbn/i18n-react'; -import { CategorizedApiKey } from '@kbn/security-plugin-types-common'; +import type { CategorizedApiKey } from '@kbn/security-plugin-types-common'; + import { TimeToolTip } from './time_tool_tip'; export type ApiKeyStatusProps = Pick; diff --git a/x-pack/packages/security/api_key_management/src/components/api_keys_api_client.ts b/x-pack/packages/security/api_key_management/src/components/api_keys_api_client.ts index 30cc9f214ebf5..11f02ffe98782 100644 --- a/x-pack/packages/security/api_key_management/src/components/api_keys_api_client.ts +++ b/x-pack/packages/security/api_key_management/src/components/api_keys_api_client.ts @@ -5,9 +5,15 @@ * 2.0. */ +import type { Criteria } from '@elastic/eui'; import type { QueryContainer } from '@elastic/eui/src/components/search_bar/query/ast_to_es_query_dsl'; import type { HttpStart } from '@kbn/core/public'; +import type { + ApiKeyToInvalidate, + CategorizedApiKey, + QueryApiKeyResult, +} from '@kbn/security-plugin-types-common'; import type { CreateAPIKeyParams, CreateAPIKeyResult, @@ -15,13 +21,6 @@ import type { UpdateAPIKeyResult, } from '@kbn/security-plugin-types-server'; -import type { - ApiKeyToInvalidate, - CategorizedApiKey, - QueryApiKeyResult, -} from '@kbn/security-plugin-types-common'; -import type { Criteria } from '@elastic/eui'; - export type { CreateAPIKeyParams, CreateAPIKeyResult, UpdateAPIKeyParams, UpdateAPIKeyResult }; export interface QueryFilters { diff --git a/x-pack/packages/security/api_key_management/src/components/time_tool_tip.tsx b/x-pack/packages/security/api_key_management/src/components/time_tool_tip.tsx index dacfd7e0c1344..0864c6b7bf1d2 100644 --- a/x-pack/packages/security/api_key_management/src/components/time_tool_tip.tsx +++ b/x-pack/packages/security/api_key_management/src/components/time_tool_tip.tsx @@ -7,8 +7,8 @@ import { EuiToolTip } from '@elastic/eui'; import moment from 'moment'; +import type { FunctionComponent } from 'react'; import React from 'react'; -import { FunctionComponent } from 'react'; export interface TimeToolTipProps { timestamp: number; diff --git a/x-pack/packages/security/authorization_core/src/privileges/privileges.ts b/x-pack/packages/security/authorization_core/src/privileges/privileges.ts index 9fb8dd9f083e2..6b8acc4e4013a 100644 --- a/x-pack/packages/security/authorization_core/src/privileges/privileges.ts +++ b/x-pack/packages/security/authorization_core/src/privileges/privileges.ts @@ -12,8 +12,8 @@ import type { FeatureKibanaPrivilegesReference, } from '@kbn/features-plugin/common'; import type { FeaturesPluginSetup, KibanaFeature } from '@kbn/features-plugin/server'; - import type { SecurityLicense } from '@kbn/security-plugin-types-common'; + import { featurePrivilegeBuilderFactory } from './feature_privilege_builder'; import type { RawKibanaPrivileges } from './raw_kibana_privileges'; import type { Actions } from '../actions'; diff --git a/x-pack/packages/security/plugin_types_public/src/authorization/authorization_service.ts b/x-pack/packages/security/plugin_types_public/src/authorization/authorization_service.ts index f04acf8020b24..71e5c7360634f 100644 --- a/x-pack/packages/security/plugin_types_public/src/authorization/authorization_service.ts +++ b/x-pack/packages/security/plugin_types_public/src/authorization/authorization_service.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { RolesAPIClient } from '../roles'; import type { PrivilegesAPIClientPublicContract } from '../privileges'; +import type { RolesAPIClient } from '../roles'; export interface AuthorizationServiceSetup { /** diff --git a/x-pack/packages/security/plugin_types_public/src/plugin.ts b/x-pack/packages/security/plugin_types_public/src/plugin.ts index 06f3574388a36..4a3351494a631 100644 --- a/x-pack/packages/security/plugin_types_public/src/plugin.ts +++ b/x-pack/packages/security/plugin_types_public/src/plugin.ts @@ -6,6 +6,7 @@ */ import type { SecurityLicense } from '@kbn/security-plugin-types-common'; + import type { AuthenticationServiceSetup, AuthenticationServiceStart } from './authentication'; import type { AuthorizationServiceSetup, AuthorizationServiceStart } from './authorization'; import type { SecurityNavControlServiceStart } from './nav_control'; diff --git a/x-pack/packages/security/plugin_types_public/src/user_profile/user_profile_api_client.ts b/x-pack/packages/security/plugin_types_public/src/user_profile/user_profile_api_client.ts index eedf6e87a6483..862bcdccf942e 100644 --- a/x-pack/packages/security/plugin_types_public/src/user_profile/user_profile_api_client.ts +++ b/x-pack/packages/security/plugin_types_public/src/user_profile/user_profile_api_client.ts @@ -5,9 +5,10 @@ * 2.0. */ +import type { Observable } from 'rxjs'; + import type { CoreUserProfileDelegateContract } from '@kbn/core-user-profile-browser'; import type { UserProfileData } from '@kbn/core-user-profile-common'; -import type { Observable } from 'rxjs'; export type { GetUserProfileResponse, diff --git a/x-pack/packages/security/plugin_types_server/src/audit/audit_service.ts b/x-pack/packages/security/plugin_types_server/src/audit/audit_service.ts index e7b7f27b73b07..89524efb06339 100644 --- a/x-pack/packages/security/plugin_types_server/src/audit/audit_service.ts +++ b/x-pack/packages/security/plugin_types_server/src/audit/audit_service.ts @@ -6,7 +6,6 @@ */ import type { KibanaRequest } from '@kbn/core/server'; - import type { AuditLogger } from '@kbn/core-security-server'; export interface AuditServiceSetup { diff --git a/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts b/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts index c331802c7f693..f8d9f085a3246 100644 --- a/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts +++ b/x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts @@ -6,7 +6,8 @@ */ import { schema } from '@kbn/config-schema'; -import { getKibanaRoleSchema, elasticsearchRoleSchema } from '../../authorization'; + +import { elasticsearchRoleSchema, getKibanaRoleSchema } from '../../authorization'; export const restApiKeySchema = schema.object({ type: schema.maybe(schema.literal('rest')), diff --git a/x-pack/packages/security/plugin_types_server/src/authentication/authentication_service.ts b/x-pack/packages/security/plugin_types_server/src/authentication/authentication_service.ts index 5d066bb6565ca..f5ee4ef7f25c1 100644 --- a/x-pack/packages/security/plugin_types_server/src/authentication/authentication_service.ts +++ b/x-pack/packages/security/plugin_types_server/src/authentication/authentication_service.ts @@ -6,8 +6,8 @@ */ import type { KibanaRequest } from '@kbn/core/server'; -import type { AuthenticatedUser } from '@kbn/security-plugin-types-common'; import type { APIKeysService } from '@kbn/core-security-server'; +import type { AuthenticatedUser } from '@kbn/security-plugin-types-common'; /** * Authentication services available on the security plugin's start contract. diff --git a/x-pack/packages/security/plugin_types_server/src/authorization/check_privileges_dynamically.ts b/x-pack/packages/security/plugin_types_server/src/authorization/check_privileges_dynamically.ts index f9663dddc64d0..df250f99bd004 100644 --- a/x-pack/packages/security/plugin_types_server/src/authorization/check_privileges_dynamically.ts +++ b/x-pack/packages/security/plugin_types_server/src/authorization/check_privileges_dynamically.ts @@ -6,9 +6,10 @@ */ import type { KibanaRequest } from '@kbn/core/server'; + import type { - CheckPrivilegesPayload, CheckPrivilegesOptions, + CheckPrivilegesPayload, CheckPrivilegesResponse, } from './check_privileges'; diff --git a/x-pack/packages/security/plugin_types_server/src/authorization/check_saved_objects_privileges.ts b/x-pack/packages/security/plugin_types_server/src/authorization/check_saved_objects_privileges.ts index 4b42723c83286..88d808b105503 100644 --- a/x-pack/packages/security/plugin_types_server/src/authorization/check_saved_objects_privileges.ts +++ b/x-pack/packages/security/plugin_types_server/src/authorization/check_saved_objects_privileges.ts @@ -6,6 +6,7 @@ */ import type { KibanaRequest } from '@kbn/core/server'; + import type { CheckPrivilegesResponse } from './check_privileges'; export type CheckSavedObjectsPrivilegesWithRequest = ( diff --git a/x-pack/packages/security/plugin_types_server/src/authorization/deprecations.ts b/x-pack/packages/security/plugin_types_server/src/authorization/deprecations.ts index 68cc61067e3c0..6567fdebf2b90 100644 --- a/x-pack/packages/security/plugin_types_server/src/authorization/deprecations.ts +++ b/x-pack/packages/security/plugin_types_server/src/authorization/deprecations.ts @@ -6,7 +6,6 @@ */ import type { DeprecationsDetails, GetDeprecationsContext } from '@kbn/core/server'; - import type { Role } from '@kbn/security-plugin-types-common'; export interface PrivilegeDeprecationsRolesByFeatureIdResponse { diff --git a/x-pack/packages/security/plugin_types_server/src/plugin.ts b/x-pack/packages/security/plugin_types_server/src/plugin.ts index c8222163785bf..7d37935ab760a 100644 --- a/x-pack/packages/security/plugin_types_server/src/plugin.ts +++ b/x-pack/packages/security/plugin_types_server/src/plugin.ts @@ -6,9 +6,10 @@ */ import type { SecurityLicense } from '@kbn/security-plugin-types-common'; + import type { AuditServiceSetup } from './audit'; -import type { PrivilegeDeprecationsService, AuthorizationServiceSetup } from './authorization'; import type { AuthenticationServiceStart } from './authentication'; +import type { AuthorizationServiceSetup, PrivilegeDeprecationsService } from './authorization'; import type { UserProfileServiceStart } from './user_profile'; /** diff --git a/x-pack/packages/security/role_management_model/src/__fixtures__/kibana_privileges.ts b/x-pack/packages/security/role_management_model/src/__fixtures__/kibana_privileges.ts index 2dc5078038033..13b34b94bf06b 100644 --- a/x-pack/packages/security/role_management_model/src/__fixtures__/kibana_privileges.ts +++ b/x-pack/packages/security/role_management_model/src/__fixtures__/kibana_privileges.ts @@ -12,8 +12,9 @@ import { subFeaturePrivilegeIterator, } from '@kbn/features-plugin/server/feature_privilege_iterator'; import type { LicenseType } from '@kbn/licensing-plugin/server'; -import type { SecurityLicenseFeatures } from '@kbn/security-plugin-types-common'; import { Actions, privilegesFactory } from '@kbn/security-authorization-core'; +import type { SecurityLicenseFeatures } from '@kbn/security-plugin-types-common'; + import { KibanaPrivileges } from '../kibana_privileges'; const featuresPluginService = (): jest.Mocked => { diff --git a/x-pack/packages/security/role_management_model/src/kibana_privileges.test.ts b/x-pack/packages/security/role_management_model/src/kibana_privileges.test.ts index 6102c853db51b..9fcd04cc2074d 100644 --- a/x-pack/packages/security/role_management_model/src/kibana_privileges.test.ts +++ b/x-pack/packages/security/role_management_model/src/kibana_privileges.test.ts @@ -5,10 +5,11 @@ * 2.0. */ -import { KibanaPrivilege } from './kibana_privilege'; -import { KibanaPrivileges, isGlobalPrivilegeDefinition } from './kibana_privileges'; import type { RoleKibanaPrivilege } from '@kbn/security-plugin-types-common'; + import { createRawKibanaPrivileges, kibanaFeatures } from './__fixtures__'; +import { KibanaPrivilege } from './kibana_privilege'; +import { isGlobalPrivilegeDefinition, KibanaPrivileges } from './kibana_privileges'; describe('kibana_privilege', () => { describe('isGlobalPrivilegeDefinition', () => { diff --git a/x-pack/packages/security/role_management_model/src/kibana_privileges.ts b/x-pack/packages/security/role_management_model/src/kibana_privileges.ts index e78ee9b105bbf..a54ee72cf308a 100644 --- a/x-pack/packages/security/role_management_model/src/kibana_privileges.ts +++ b/x-pack/packages/security/role_management_model/src/kibana_privileges.ts @@ -6,9 +6,9 @@ */ import type { KibanaFeature } from '@kbn/features-plugin/common'; - -import type { RoleKibanaPrivilege } from '@kbn/security-plugin-types-common'; import type { RawKibanaPrivileges } from '@kbn/security-authorization-core'; +import type { RoleKibanaPrivilege } from '@kbn/security-plugin-types-common'; + import { KibanaPrivilege } from './kibana_privilege'; import { PrivilegeCollection } from './privilege_collection'; import { SecuredFeature } from './secured_feature'; diff --git a/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.test.tsx b/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.test.tsx index 2380088dd713f..2c858e7bb6ff6 100644 --- a/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.test.tsx +++ b/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.test.tsx @@ -9,13 +9,13 @@ import { EuiAccordion, EuiIconTip } from '@elastic/eui'; import React from 'react'; import type { KibanaFeature, SubFeatureConfig } from '@kbn/features-plugin/public'; +import type { Role } from '@kbn/security-plugin-types-common'; import { createFeature, createKibanaPrivileges, kibanaFeatures, } from '@kbn/security-role-management-model/src/__fixtures__'; import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; -import type { Role } from '@kbn/security-plugin-types-common'; import { getDisplayedFeaturePrivileges } from './__fixtures__'; import { FeatureTable } from './feature_table'; diff --git a/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.tsx b/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.tsx index 6fef00ccecec9..2f77b55ce5bac 100644 --- a/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.tsx +++ b/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.tsx @@ -33,9 +33,9 @@ import type { Role } from '@kbn/security-plugin-types-common'; import type { KibanaPrivileges, SecuredFeature } from '@kbn/security-role-management-model'; import { ChangeAllPrivilegesControl } from './change_all_privileges'; +import { FeatureTableCell } from './components/feature_table_cell'; import { FeatureTableExpandedRow } from './feature_table_expanded_row'; import { NO_PRIVILEGE_VALUE } from '../constants'; -import { FeatureTableCell } from './components/feature_table_cell'; import type { PrivilegeFormCalculator } from '../privilege_form_calculator'; interface Props { diff --git a/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table_expanded_row.test.tsx b/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table_expanded_row.test.tsx index 5e4f4ce021d44..e4f1755604dc5 100644 --- a/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table_expanded_row.test.tsx +++ b/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table_expanded_row.test.tsx @@ -8,11 +8,11 @@ import { act } from '@testing-library/react'; import React from 'react'; +import type { Role } from '@kbn/security-plugin-types-common'; import { createKibanaPrivileges, kibanaFeatures, } from '@kbn/security-role-management-model/src/__fixtures__'; -import type { Role } from '@kbn/security-plugin-types-common'; import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import { FeatureTableExpandedRow } from './feature_table_expanded_row'; diff --git a/x-pack/packages/security/ui_components/src/privilege_form_calculator/privilege_form_calculator.test.ts b/x-pack/packages/security/ui_components/src/privilege_form_calculator/privilege_form_calculator.test.ts index e61134b816ffa..7977ee693721b 100644 --- a/x-pack/packages/security/ui_components/src/privilege_form_calculator/privilege_form_calculator.test.ts +++ b/x-pack/packages/security/ui_components/src/privilege_form_calculator/privilege_form_calculator.test.ts @@ -5,11 +5,11 @@ * 2.0. */ +import type { Role } from '@kbn/security-plugin-types-common'; import { createKibanaPrivileges, kibanaFeatures, } from '@kbn/security-role-management-model/src/__fixtures__'; -import type { Role } from '@kbn/security-plugin-types-common'; import { PrivilegeFormCalculator } from './privilege_form_calculator'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts index f6f27e15ee7a4..955f5a45a9743 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts @@ -21,6 +21,7 @@ import type { CspFinding } from '@kbn/cloud-security-posture-common'; import type { CspBenchmarkRulesStates } from '@kbn/cloud-security-posture-common/schema/rules/latest'; import type { FindingsBaseEsQuery } from '@kbn/cloud-security-posture'; import { useGetCspBenchmarkRulesStatesApi } from '@kbn/cloud-security-posture/src/hooks/use_get_benchmark_rules_state_api'; +import type { RuntimePrimitiveTypes } from '@kbn/data-views-plugin/common'; import { useKibana } from '../../../common/hooks/use_kibana'; import { getAggregationCount, getFindingsCountAggQuery } from '../utils/utils'; @@ -39,6 +40,20 @@ interface FindingsAggs { count: estypes.AggregationsMultiBucketAggregateBase; } +const getRuntimeMappingsFromSort = (sort: string[][]) => { + return sort.reduce((acc, [field]) => { + // TODO: Add proper type for all fields available in the field selector + const type: RuntimePrimitiveTypes = field === '@timestamp' ? 'date' : 'keyword'; + + return { + ...acc, + [field]: { + type, + }, + }; + }, {}); +}; + export const getFindingsQuery = ( { query, sort }: UseFindingsOptions, rulesStates: CspBenchmarkRulesStates, @@ -49,6 +64,7 @@ export const getFindingsQuery = ( return { index: CDR_MISCONFIGURATIONS_INDEX_PATTERN, sort: getMultiFieldsSort(sort), + runtime_mappings: getRuntimeMappingsFromSort(sort), size: MAX_FINDINGS_TO_LOAD, aggs: getFindingsCountAggQuery(), ignore_unavailable: true, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings_grouping.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings_grouping.tsx index cc409fb95024d..6482d864347a1 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings_grouping.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings_grouping.tsx @@ -114,6 +114,72 @@ const getAggregationsByGroupField = (field: string): NamedAggregation[] => { return aggMetrics; }; +/** + * Get runtime mappings for the given group field + * Some fields require additional runtime mappings to aggregate additional information + * Fallback to keyword type to support custom fields grouping + */ +const getRuntimeMappingsByGroupField = ( + field: string +): Record | undefined => { + switch (field) { + case FINDINGS_GROUPING_OPTIONS.RESOURCE_NAME: + return { + [FINDINGS_GROUPING_OPTIONS.RESOURCE_NAME]: { + type: 'keyword', + }, + 'resource.id': { + type: 'keyword', + }, + 'resource.sub_type': { + type: 'keyword', + }, + 'resource.type': { + type: 'keyword', + }, + }; + case FINDINGS_GROUPING_OPTIONS.RULE_NAME: + return { + [FINDINGS_GROUPING_OPTIONS.RULE_NAME]: { + type: 'keyword', + }, + 'rule.benchmark.version': { + type: 'keyword', + }, + }; + case FINDINGS_GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME: + return { + [FINDINGS_GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME]: { + type: 'keyword', + }, + 'rule.benchmark.name': { + type: 'keyword', + }, + 'rule.benchmark.id': { + type: 'keyword', + }, + }; + case FINDINGS_GROUPING_OPTIONS.ORCHESTRATOR_CLUSTER_NAME: + return { + [FINDINGS_GROUPING_OPTIONS.ORCHESTRATOR_CLUSTER_NAME]: { + type: 'keyword', + }, + 'rule.benchmark.name': { + type: 'keyword', + }, + 'rule.benchmark.id': { + type: 'keyword', + }, + }; + default: + return { + [field]: { + type: 'keyword', + }, + }; + } +}; + /** * Type Guard for checking if the given source is a FindingsRootGroupingAggregation */ @@ -189,6 +255,12 @@ export const useLatestFindingsGrouping = ({ size: pageSize, sort: [{ groupByField: { order: 'desc' } }, { complianceScore: { order: 'asc' } }], statsAggregations: getAggregationsByGroupField(currentSelectedGroup), + runtimeMappings: { + ...getRuntimeMappingsByGroupField(currentSelectedGroup), + 'result.evaluation': { + type: 'keyword', + }, + }, rootAggregations: [ { failedFindings: { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities.tsx index 0d0ea9ba5a22f..0b9cf6978c258 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities.tsx @@ -23,6 +23,7 @@ import { } from '@kbn/cloud-security-posture-common'; import { FindingsBaseEsQuery, showErrorToast } from '@kbn/cloud-security-posture'; import type { CspVulnerabilityFinding } from '@kbn/cloud-security-posture-common/schema/vulnerabilities/latest'; +import type { RuntimePrimitiveTypes } from '@kbn/data-views-plugin/common'; import { VULNERABILITY_FIELDS } from '../../../common/constants'; import { useKibana } from '../../../common/hooks/use_kibana'; import { getCaseInsensitiveSortScript } from '../utils/custom_sort_script'; @@ -52,6 +53,25 @@ const getMultiFieldsSort = (sort: string[][]) => { }); }; +const getRuntimeMappingsFromSort = (sort: string[][]) => { + return sort.reduce((acc, [field]) => { + // TODO: Add proper type for all fields available in the field selector + const type: RuntimePrimitiveTypes = + field === VULNERABILITY_FIELDS.SCORE_BASE + ? 'double' + : field === '@timestamp' + ? 'date' + : 'keyword'; + + return { + ...acc, + [field]: { + type, + }, + }; + }, {}); +}; + export const getVulnerabilitiesQuery = ( { query, sort }: VulnerabilitiesQuery, pageParam: number @@ -59,6 +79,7 @@ export const getVulnerabilitiesQuery = ( index: CDR_VULNERABILITIES_INDEX_PATTERN, ignore_unavailable: true, sort: getMultiFieldsSort(sort), + runtime_mappings: getRuntimeMappingsFromSort(sort), size: MAX_FINDINGS_TO_LOAD, query: { ...query, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_grouping.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_grouping.tsx index 516cbed0c3975..3c52590f8fd80 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_grouping.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_grouping.tsx @@ -94,6 +94,51 @@ const getAggregationsByGroupField = (field: string): NamedAggregation[] => { return aggMetrics; }; +/** + * Get runtime mappings for the given group field + * Some fields require additional runtime mappings to aggregate additional information + * Fallback to keyword type to support custom fields grouping + */ +const getRuntimeMappingsByGroupField = ( + field: string +): Record | undefined => { + switch (field) { + case VULNERABILITY_GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME: + return { + [VULNERABILITY_GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME]: { + type: 'keyword', + }, + [VULNERABILITY_FIELDS.CLOUD_PROVIDER]: { + type: 'keyword', + }, + }; + case VULNERABILITY_GROUPING_OPTIONS.RESOURCE_NAME: + return { + [VULNERABILITY_GROUPING_OPTIONS.RESOURCE_NAME]: { + type: 'keyword', + }, + [VULNERABILITY_FIELDS.RESOURCE_ID]: { + type: 'keyword', + }, + }; + case VULNERABILITY_GROUPING_OPTIONS.CVE: + return { + [VULNERABILITY_GROUPING_OPTIONS.CVE]: { + type: 'keyword', + }, + [VULNERABILITY_FIELDS.DESCRIPTION]: { + type: 'keyword', + }, + }; + default: + return { + [field]: { + type: 'keyword', + }, + }; + } +}; + /** * Type Guard for checking if the given source is a VulnerabilitiesRootGroupingAggregation */ @@ -163,6 +208,7 @@ export const useLatestVulnerabilitiesGrouping = ({ size: pageSize, sort: [{ groupByField: { order: 'desc' } }], statsAggregations: getAggregationsByGroupField(currentSelectedGroup), + runtimeMappings: getRuntimeMappingsByGroupField(currentSelectedGroup), }); const { data, isFetching } = useGroupedVulnerabilities({ diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts index 780cd539305b3..e517d622e71c5 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts @@ -14,7 +14,13 @@ export const getCaseInsensitiveSortScript = (field: string, direction: string) = type: 'string', order: direction, script: { - source: `doc["${field}"].value.toLowerCase()`, + source: ` + if (doc.containsKey('${field}') && !doc['${field}'].empty) { + return doc['${field}'].value.toLowerCase(); + } else { + return ""; + } + `, lang: 'painless', }, }, diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js index 351f1bd77592f..c0b6c0f4c9a09 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js @@ -30,8 +30,6 @@ import { EuiTextColor, EuiTitle, } from '@elastic/eui'; -import 'react-ace'; -import 'brace/theme/textmate'; import { getIndexListUri } from '@kbn/index-management-plugin/public'; import { routing } from '../../../../../services/routing'; diff --git a/x-pack/plugins/data_usage/common/rest_types/usage_metrics.test.ts b/x-pack/plugins/data_usage/common/rest_types/usage_metrics.test.ts index f6c08e2caddc0..473e64c6b03d9 100644 --- a/x-pack/plugins/data_usage/common/rest_types/usage_metrics.test.ts +++ b/x-pack/plugins/data_usage/common/rest_types/usage_metrics.test.ts @@ -10,48 +10,29 @@ import { UsageMetricsRequestSchema } from './usage_metrics'; describe('usage_metrics schemas', () => { it('should accept valid request query', () => { expect(() => - UsageMetricsRequestSchema.query.validate({ + UsageMetricsRequestSchema.validate({ from: new Date().toISOString(), to: new Date().toISOString(), metricTypes: ['storage_retained'], - }) - ).not.toThrow(); - }); - - it('should accept a single `metricTypes` in request query', () => { - expect(() => - UsageMetricsRequestSchema.query.validate({ - from: new Date().toISOString(), - to: new Date().toISOString(), - metricTypes: 'ingest_rate', + dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'], }) ).not.toThrow(); }); it('should accept multiple `metricTypes` in request query', () => { expect(() => - UsageMetricsRequestSchema.query.validate({ + UsageMetricsRequestSchema.validate({ from: new Date().toISOString(), to: new Date().toISOString(), metricTypes: ['ingest_rate', 'storage_retained', 'index_rate'], - }) - ).not.toThrow(); - }); - - it('should accept a single string as `dataStreams` in request query', () => { - expect(() => - UsageMetricsRequestSchema.query.validate({ - from: new Date().toISOString(), - to: new Date().toISOString(), - metricTypes: 'storage_retained', - dataStreams: 'data_stream_1', + dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'], }) ).not.toThrow(); }); it('should accept `dataStream` list', () => { expect(() => - UsageMetricsRequestSchema.query.validate({ + UsageMetricsRequestSchema.validate({ from: new Date().toISOString(), to: new Date().toISOString(), metricTypes: ['storage_retained'], @@ -62,74 +43,76 @@ describe('usage_metrics schemas', () => { it('should error if `dataStream` list is empty', () => { expect(() => - UsageMetricsRequestSchema.query.validate({ + UsageMetricsRequestSchema.validate({ from: new Date().toISOString(), to: new Date().toISOString(), metricTypes: ['storage_retained'], dataStreams: [], }) - ).toThrowError('expected value of type [string] but got [Array]'); + ).toThrowError('[dataStreams]: array size is [0], but cannot be smaller than [1]'); }); - it('should error if `dataStream` is given an empty string', () => { + it('should error if `dataStream` is given type not array', () => { expect(() => - UsageMetricsRequestSchema.query.validate({ + UsageMetricsRequestSchema.validate({ from: new Date().toISOString(), to: new Date().toISOString(), metricTypes: ['storage_retained'], dataStreams: ' ', }) - ).toThrow('[dataStreams] must have at least one value'); + ).toThrow('[dataStreams]: could not parse array value from json input'); }); it('should error if `dataStream` is given an empty item in the list', () => { expect(() => - UsageMetricsRequestSchema.query.validate({ + UsageMetricsRequestSchema.validate({ from: new Date().toISOString(), to: new Date().toISOString(), metricTypes: ['storage_retained'], dataStreams: ['ds_1', ' '], }) - ).toThrow('[dataStreams] list can not contain empty values'); + ).toThrow('[dataStreams]: [dataStreams] list cannot contain empty values'); }); it('should error if `metricTypes` is empty string', () => { expect(() => - UsageMetricsRequestSchema.query.validate({ + UsageMetricsRequestSchema.validate({ from: new Date().toISOString(), to: new Date().toISOString(), + dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'], metricTypes: ' ', }) ).toThrow(); }); - it('should error if `metricTypes` is empty item', () => { + it('should error if `metricTypes` contains an empty item', () => { expect(() => - UsageMetricsRequestSchema.query.validate({ + UsageMetricsRequestSchema.validate({ from: new Date().toISOString(), to: new Date().toISOString(), - metricTypes: [' ', 'storage_retained'], + dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'], + metricTypes: [' ', 'storage_retained'], // First item is invalid }) - ).toThrow('[metricTypes] list can not contain empty values'); + ).toThrowError(/list cannot contain empty values/); }); - it('should error if `metricTypes` is not a valid value', () => { + it('should error if `metricTypes` is not a valid type', () => { expect(() => - UsageMetricsRequestSchema.query.validate({ + UsageMetricsRequestSchema.validate({ from: new Date().toISOString(), to: new Date().toISOString(), + dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'], metricTypes: 'foo', }) - ).toThrow( - '[metricTypes] must be one of storage_retained, ingest_rate, search_vcu, ingest_vcu, ml_vcu, index_latency, index_rate, search_latency, search_rate' - ); + ).toThrow('[metricTypes]: could not parse array value from json input'); }); it('should error if `metricTypes` is not a valid list', () => { expect(() => - UsageMetricsRequestSchema.query.validate({ + UsageMetricsRequestSchema.validate({ from: new Date().toISOString(), to: new Date().toISOString(), + dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'], metricTypes: ['storage_retained', 'foo'], }) ).toThrow( @@ -139,9 +122,10 @@ describe('usage_metrics schemas', () => { it('should error if `from` is not a valid input', () => { expect(() => - UsageMetricsRequestSchema.query.validate({ + UsageMetricsRequestSchema.validate({ from: 1010, to: new Date().toISOString(), + dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'], metricTypes: ['storage_retained', 'foo'], }) ).toThrow('[from]: expected value of type [string] but got [number]'); @@ -149,9 +133,10 @@ describe('usage_metrics schemas', () => { it('should error if `to` is not a valid input', () => { expect(() => - UsageMetricsRequestSchema.query.validate({ + UsageMetricsRequestSchema.validate({ from: new Date().toISOString(), to: 1010, + dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'], metricTypes: ['storage_retained', 'foo'], }) ).toThrow('[to]: expected value of type [string] but got [number]'); @@ -159,9 +144,10 @@ describe('usage_metrics schemas', () => { it('should error if `from` is empty string', () => { expect(() => - UsageMetricsRequestSchema.query.validate({ + UsageMetricsRequestSchema.validate({ from: ' ', to: new Date().toISOString(), + dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'], metricTypes: ['storage_retained', 'foo'], }) ).toThrow('[from]: Date ISO string must not be empty'); @@ -169,9 +155,10 @@ describe('usage_metrics schemas', () => { it('should error if `to` is empty string', () => { expect(() => - UsageMetricsRequestSchema.query.validate({ + UsageMetricsRequestSchema.validate({ from: new Date().toISOString(), to: ' ', + dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'], metricTypes: ['storage_retained', 'foo'], }) ).toThrow('[to]: Date ISO string must not be empty'); diff --git a/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts b/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts index f2bbdb616fc79..3dceeadc198b0 100644 --- a/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts +++ b/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts @@ -37,51 +37,31 @@ const metricTypesSchema = schema.oneOf( // @ts-expect-error TS2769: No overload matches this call METRIC_TYPE_VALUES.map((metricType) => schema.literal(metricType)) // Create a oneOf schema for the keys ); -export const UsageMetricsRequestSchema = { - query: schema.object({ - from: DateSchema, - to: DateSchema, - metricTypes: schema.oneOf([ - schema.arrayOf(schema.string(), { - minSize: 1, - validate: (values) => { - if (values.map((v) => v.trim()).some((v) => !v.length)) { - return '[metricTypes] list can not contain empty values'; - } else if (values.map((v) => v.trim()).some((v) => !isValidMetricType(v))) { - return `[metricTypes] must be one of ${METRIC_TYPE_VALUES.join(', ')}`; - } - }, - }), - schema.string({ - validate: (v) => { - if (!v.trim().length) { - return '[metricTypes] must have at least one value'; - } else if (!isValidMetricType(v)) { - return `[metricTypes] must be one of ${METRIC_TYPE_VALUES.join(', ')}`; - } - }, - }), - ]), - dataStreams: schema.maybe( - schema.oneOf([ - schema.arrayOf(schema.string(), { - minSize: 1, - validate: (values) => { - if (values.map((v) => v.trim()).some((v) => !v.length)) { - return '[dataStreams] list can not contain empty values'; - } - }, - }), - schema.string({ - validate: (v) => - v.trim().length ? undefined : '[dataStreams] must have at least one value', - }), - ]) - ), +export const UsageMetricsRequestSchema = schema.object({ + from: DateSchema, + to: DateSchema, + metricTypes: schema.arrayOf(schema.string(), { + minSize: 1, + validate: (values) => { + const trimmedValues = values.map((v) => v.trim()); + if (trimmedValues.some((v) => !v.length)) { + return '[metricTypes] list cannot contain empty values'; + } else if (trimmedValues.some((v) => !isValidMetricType(v))) { + return `[metricTypes] must be one of ${METRIC_TYPE_VALUES.join(', ')}`; + } + }, }), -}; + dataStreams: schema.arrayOf(schema.string(), { + minSize: 1, + validate: (values) => { + if (values.map((v) => v.trim()).some((v) => !v.length)) { + return '[dataStreams] list cannot contain empty values'; + } + }, + }), +}); -export type UsageMetricsRequestSchemaQueryParams = TypeOf; +export type UsageMetricsRequestSchemaQueryParams = TypeOf; export const UsageMetricsResponseSchema = { body: () => @@ -92,11 +72,40 @@ export const UsageMetricsResponseSchema = { schema.object({ name: schema.string(), data: schema.arrayOf( - schema.arrayOf(schema.number(), { minSize: 2, maxSize: 2 }) // Each data point is an array of 2 numbers + schema.object({ + x: schema.number(), + y: schema.number(), + }) ), }) ) ), }), }; -export type UsageMetricsResponseSchemaBody = TypeOf; +export type UsageMetricsResponseSchemaBody = Omit< + TypeOf, + 'metrics' +> & { + metrics: Partial>; +}; +export type MetricSeries = TypeOf< + typeof UsageMetricsResponseSchema.body +>['metrics'][MetricTypes][number]; + +export const UsageMetricsAutoOpsResponseSchema = { + body: () => + schema.object({ + metrics: schema.recordOf( + metricTypesSchema, + schema.arrayOf( + schema.object({ + name: schema.string(), + data: schema.arrayOf(schema.arrayOf(schema.number(), { minSize: 2, maxSize: 2 })), + }) + ) + ), + }), +}; +export type UsageMetricsAutoOpsResponseSchemaBody = TypeOf< + typeof UsageMetricsAutoOpsResponseSchema.body +>; diff --git a/x-pack/plugins/data_usage/public/app/components/chart_panel.tsx b/x-pack/plugins/data_usage/public/app/components/chart_panel.tsx index c7937ae149de9..1ba3f0fe3f454 100644 --- a/x-pack/plugins/data_usage/public/app/components/chart_panel.tsx +++ b/x-pack/plugins/data_usage/public/app/components/chart_panel.tsx @@ -19,8 +19,7 @@ import { } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; import { LegendAction } from './legend_action'; -import { MetricTypes } from '../../../common/rest_types'; -import { MetricSeries } from '../types'; +import { MetricTypes, MetricSeries } from '../../../common/rest_types'; // TODO: Remove this when we have a title for each metric type type ChartKey = Extract; @@ -50,7 +49,7 @@ export const ChartPanel: React.FC = ({ }) => { const theme = useEuiTheme(); - const chartTimestamps = series.flatMap((stream) => stream.data.map((d) => d[0])); + const chartTimestamps = series.flatMap((stream) => stream.data.map((d) => d.x)); const [minTimestamp, maxTimestamp] = [Math.min(...chartTimestamps), Math.max(...chartTimestamps)]; @@ -72,6 +71,7 @@ export const ChartPanel: React.FC = ({ }, [idx, popoverOpen, togglePopover] ); + return ( @@ -94,9 +94,9 @@ export const ChartPanel: React.FC = ({ data={stream.data} xScaleType={ScaleType.Time} yScaleType={ScaleType.Linear} - xAccessor={0} // x is the first element in the tuple - yAccessors={[1]} // y is the second element in the tuple - stackAccessors={[0]} + xAccessor="x" + yAccessors={['y']} + stackAccessors={['x']} /> ))} @@ -118,6 +118,7 @@ export const ChartPanel: React.FC = ({ ); }; + const formatBytes = (bytes: number) => { return numeral(bytes).format('0.0 b'); }; diff --git a/x-pack/plugins/data_usage/public/app/components/charts.tsx b/x-pack/plugins/data_usage/public/app/components/charts.tsx index 6549f7e03830a..8d04324fb2246 100644 --- a/x-pack/plugins/data_usage/public/app/components/charts.tsx +++ b/x-pack/plugins/data_usage/public/app/components/charts.tsx @@ -6,11 +6,11 @@ */ import React, { useCallback, useState } from 'react'; import { EuiFlexGroup } from '@elastic/eui'; -import { MetricsResponse } from '../types'; import { MetricTypes } from '../../../common/rest_types'; import { ChartPanel } from './chart_panel'; +import { UsageMetricsResponseSchemaBody } from '../../../common/rest_types'; interface ChartsProps { - data: MetricsResponse; + data: UsageMetricsResponseSchemaBody; } export const Charts: React.FC = ({ data }) => { diff --git a/x-pack/plugins/data_usage/public/app/data_usage.tsx b/x-pack/plugins/data_usage/public/app/data_usage.tsx index c32f86d68b5bf..bea9f2b511a77 100644 --- a/x-pack/plugins/data_usage/public/app/data_usage.tsx +++ b/x-pack/plugins/data_usage/public/app/data_usage.tsx @@ -26,7 +26,6 @@ import { PLUGIN_NAME } from '../../common'; import { useGetDataUsageMetrics } from '../hooks/use_get_usage_metrics'; import { DEFAULT_DATE_RANGE_OPTIONS, useDateRangePicker } from './hooks/use_date_picker'; import { useDataUsageMetricsUrlParams } from './hooks/use_charts_url_params'; -import { MetricsResponse } from './types'; export const DataUsage = () => { const { @@ -42,37 +41,37 @@ export const DataUsage = () => { setUrlDateRangeFilter, } = useDataUsageMetricsUrlParams(); - const [queryParams, setQueryParams] = useState({ + const [metricsFilters, setMetricsFilters] = useState({ metricTypes: ['storage_retained', 'ingest_rate'], - dataStreams: [], + // TODO: Replace with data streams from /data_streams api + dataStreams: [ + '.alerts-ml.anomaly-detection-health.alerts-default', + '.alerts-stack.alerts-default', + ], from: DEFAULT_DATE_RANGE_OPTIONS.startDate, to: DEFAULT_DATE_RANGE_OPTIONS.endDate, }); useEffect(() => { if (!metricTypesFromUrl) { - setUrlMetricTypesFilter( - typeof queryParams.metricTypes !== 'string' - ? queryParams.metricTypes.join(',') - : queryParams.metricTypes - ); + setUrlMetricTypesFilter(metricsFilters.metricTypes.join(',')); } if (!startDateFromUrl || !endDateFromUrl) { - setUrlDateRangeFilter({ startDate: queryParams.from, endDate: queryParams.to }); + setUrlDateRangeFilter({ startDate: metricsFilters.from, endDate: metricsFilters.to }); } }, [ endDateFromUrl, metricTypesFromUrl, - queryParams.from, - queryParams.metricTypes, - queryParams.to, + metricsFilters.from, + metricsFilters.metricTypes, + metricsFilters.to, setUrlDateRangeFilter, setUrlMetricTypesFilter, startDateFromUrl, ]); useEffect(() => { - setQueryParams((prevState) => ({ + setMetricsFilters((prevState) => ({ ...prevState, metricTypes: metricTypesFromUrl?.length ? metricTypesFromUrl : prevState.metricTypes, dataStreams: dataStreamsFromUrl?.length ? dataStreamsFromUrl : prevState.dataStreams, @@ -89,7 +88,7 @@ export const DataUsage = () => { refetch: refetchDataUsageMetrics, } = useGetDataUsageMetrics( { - ...queryParams, + ...metricsFilters, from: dateRangePickerState.startDate, to: dateRangePickerState.endDate, }, @@ -140,7 +139,7 @@ export const DataUsage = () => { - {isFetched && data ? : } + {isFetched && data ? : } ); diff --git a/x-pack/plugins/data_usage/public/app/types.ts b/x-pack/plugins/data_usage/public/app/types.ts deleted file mode 100644 index 13f53bc2ea6dd..0000000000000 --- a/x-pack/plugins/data_usage/public/app/types.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { MetricTypes } from '../../common/rest_types'; - -export type DataPoint = [number, number]; // [timestamp, value] - -export interface MetricSeries { - name: string; // Name of the data stream - data: DataPoint[]; // Array of data points in tuple format [timestamp, value] -} -// Use MetricTypes dynamically as keys for the Metrics interface -export type Metrics = Partial>; - -export interface MetricsResponse { - metrics: Metrics; -} -export interface MetricsResponse { - metrics: Metrics; -} diff --git a/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts b/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts index 6b9860e997c12..3d648eb183f07 100644 --- a/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts +++ b/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts @@ -21,24 +21,24 @@ interface ErrorType { } export const useGetDataUsageMetrics = ( - query: UsageMetricsRequestSchemaQueryParams, + body: UsageMetricsRequestSchemaQueryParams, options: UseQueryOptions> = {} ): UseQueryResult> => { const http = useKibanaContextForPlugin().services.http; return useQuery>({ - queryKey: ['get-data-usage-metrics', query], + queryKey: ['get-data-usage-metrics', body], ...options, keepPreviousData: true, queryFn: async () => { - return http.get(DATA_USAGE_METRICS_API_ROUTE, { + return http.post(DATA_USAGE_METRICS_API_ROUTE, { version: '1', - query: { - from: query.from, - to: query.to, - metricTypes: query.metricTypes, - dataStreams: query.dataStreams, - }, + body: JSON.stringify({ + from: body.from, + to: body.to, + metricTypes: body.metricTypes, + dataStreams: body.dataStreams, + }), }); }, }); diff --git a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.ts b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.ts index 5bf3008ef668a..0013102f697fb 100644 --- a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.ts +++ b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.ts @@ -17,7 +17,7 @@ export const registerUsageMetricsRoute = ( ) => { if (dataUsageContext.serverConfig.enabled) { router.versioned - .get({ + .post({ access: 'internal', path: DATA_USAGE_METRICS_API_ROUTE, }) @@ -25,7 +25,9 @@ export const registerUsageMetricsRoute = ( { version: '1', validate: { - request: UsageMetricsRequestSchema, + request: { + body: UsageMetricsRequestSchema, + }, response: { 200: UsageMetricsResponseSchema, }, diff --git a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts index 6f992c9fb2a38..09e9f88721c63 100644 --- a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts +++ b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts @@ -9,8 +9,10 @@ import { RequestHandler } from '@kbn/core/server'; import { IndicesGetDataStreamResponse } from '@elastic/elasticsearch/lib/api/types'; import { MetricTypes, + UsageMetricsAutoOpsResponseSchema, + UsageMetricsAutoOpsResponseSchemaBody, UsageMetricsRequestSchemaQueryParams, - UsageMetricsResponseSchema, + UsageMetricsResponseSchemaBody, } from '../../../common/rest_types'; import { DataUsageContext, DataUsageRequestHandlerContext } from '../../types'; @@ -34,45 +36,26 @@ export const getUsageMetricsHandler = ( const core = await context.core; const esClient = core.elasticsearch.client.asCurrentUser; - // @ts-ignore - const { from, to, metricTypes, dataStreams: dsNames, size } = request.query; + const { from, to, metricTypes, dataStreams: requestDsNames } = request.query; logger.debug(`Retrieving usage metrics`); const { data_streams: dataStreamsResponse }: IndicesGetDataStreamResponse = await esClient.indices.getDataStream({ - name: '*', + name: requestDsNames, expand_wildcards: 'all', }); - const hasDataStreams = dataStreamsResponse.length > 0; - let userDsNames: string[] = []; - - if (dsNames?.length) { - userDsNames = typeof dsNames === 'string' ? [dsNames] : dsNames; - } else if (!userDsNames.length && hasDataStreams) { - userDsNames = dataStreamsResponse.map((ds) => ds.name); - } - - // If no data streams are found, return an empty response - if (!userDsNames.length) { - return response.ok({ - body: { - metrics: {}, - }, - }); - } - const metrics = await fetchMetricsFromAutoOps({ from, to, metricTypes: formatStringParams(metricTypes) as MetricTypes[], - dataStreams: formatStringParams(userDsNames), + dataStreams: formatStringParams(dataStreamsResponse.map((ds) => ds.name)), }); + const processedMetrics = transformMetricsData(metrics); + return response.ok({ - body: { - metrics, - }, + body: processedMetrics, }); } catch (error) { logger.error(`Error retrieving usage metrics: ${error.message}`); @@ -94,7 +77,7 @@ const fetchMetricsFromAutoOps = async ({ }) => { // TODO: fetch data from autoOps using userDsNames /* - const response = await axios.post('https://api.auto-ops.{region}.{csp}.cloud.elastic.co/monitoring/serverless/v1/projects/{project_id}/metrics', { + const response = await axios.post({AUTOOPS_URL}, { from: Date.parse(from), to: Date.parse(to), metric_types: metricTypes, @@ -231,7 +214,25 @@ const fetchMetricsFromAutoOps = async ({ }, }; // Make sure data is what we expect - const validatedData = UsageMetricsResponseSchema.body().validate(mockData); + const validatedData = UsageMetricsAutoOpsResponseSchema.body().validate(mockData); - return validatedData.metrics; + return validatedData; }; +function transformMetricsData( + data: UsageMetricsAutoOpsResponseSchemaBody +): UsageMetricsResponseSchemaBody { + return { + metrics: Object.fromEntries( + Object.entries(data.metrics).map(([metricType, series]) => [ + metricType, + series.map((metricSeries) => ({ + name: metricSeries.name, + data: (metricSeries.data as Array<[number, number]>).map(([timestamp, value]) => ({ + x: timestamp, + y: value, + })), + })), + ]) + ), + }; +} diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts index ada5b8a421441..4f043c681f8df 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts @@ -103,7 +103,6 @@ export const callAssistantGraph: AgentExecutor = async ({ isEnabledKnowledgeBase, kbDataClient: dataClients?.kbDataClient, logger, - modelExists: isEnabledKnowledgeBase, onNewReplacements, replacements, request, diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts index 15877e6727715..d5eaf7d159618 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.test.ts @@ -196,7 +196,6 @@ describe('helpers', () => { langChainTimeout, llm, logger: mockLogger, - modelExists: false, onNewReplacements, replacements: latestReplacements, request: mockRequest, @@ -231,7 +230,6 @@ describe('helpers', () => { langChainTimeout, llm, logger: mockLogger, - modelExists: false, onNewReplacements, replacements: latestReplacements, request: mockRequest, diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts index 2a1450a9f7b9b..f016d6ac29118 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts @@ -157,7 +157,6 @@ const formatAssistantToolParams = ({ langChainTimeout, llm, logger, - modelExists: false, // not required for attack discovery onNewReplacements, replacements: latestReplacements, request, diff --git a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts index de154a1ddd96d..29a7527964677 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/evaluate/post_evaluate.ts @@ -236,7 +236,6 @@ export const postEvaluateRoute = ( llm, isOssModel, logger, - modelExists: isEnabledKnowledgeBase, request: skeletonRequest, alertsIndexPattern, // onNewReplacements, diff --git a/x-pack/plugins/elastic_assistant/server/types.ts b/x-pack/plugins/elastic_assistant/server/types.ts index 3117295810877..45bd5a4149b58 100755 --- a/x-pack/plugins/elastic_assistant/server/types.ts +++ b/x-pack/plugins/elastic_assistant/server/types.ts @@ -244,7 +244,6 @@ export interface AssistantToolParams { llm?: ActionsClientLlm | AssistantToolLlm; isOssModel?: boolean; logger: Logger; - modelExists: boolean; onNewReplacements?: (newReplacements: Replacements) => void; replacements?: Replacements; request: KibanaRequest< diff --git a/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts b/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts index e6263521f690d..4d453f64b6954 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { BuildFlavor } from '@kbn/config'; +import type { BuildFlavor } from '@kbn/config'; import { httpServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; import type { ConfigType } from '../config'; diff --git a/x-pack/plugins/encrypted_saved_objects/server/routes/index.ts b/x-pack/plugins/encrypted_saved_objects/server/routes/index.ts index 14d1933e8b765..aa3499ee46055 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/routes/index.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/routes/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { BuildFlavor } from '@kbn/config'; +import type { BuildFlavor } from '@kbn/config'; import type { IRouter, Logger } from '@kbn/core/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; diff --git a/x-pack/plugins/fleet/cypress.config.space_awareness.ts b/x-pack/plugins/fleet/cypress.config.space_awareness.ts new file mode 100644 index 0000000000000..6efda828e6fbc --- /dev/null +++ b/x-pack/plugins/fleet/cypress.config.space_awareness.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { defineCypressConfig } from '@kbn/cypress-config'; + +// eslint-disable-next-line import/no-default-export +export default defineCypressConfig({ + defaultCommandTimeout: 60000, + requestTimeout: 60000, + responseTimeout: 60000, + execTimeout: 120000, + pageLoadTimeout: 120000, + + retries: { + runMode: 2, + }, + + env: { + grepFilterSpecs: false, + }, + + screenshotsFolder: '../../../target/kibana-fleet/cypress/screenshots', + trashAssetsBeforeRuns: false, + video: false, + videosFolder: '../../../target/kibana-fleet/cypress/videos', + viewportHeight: 900, + viewportWidth: 1440, + screenshotOnRunFailure: true, + + e2e: { + baseUrl: 'http://localhost:5601', + + experimentalRunAllSpecs: true, + experimentalMemoryManagement: true, + numTestsKeptInMemory: 3, + + specPattern: './cypress/e2e/space_awareness/**/*.cy.ts', + supportFile: './cypress/support/e2e.ts', + + setupNodeEvents(on, config) { + // eslint-disable-next-line @typescript-eslint/no-var-requires, @kbn/imports/no_boundary_crossing + return require('./cypress/plugins')(on, config); + }, + }, +}); diff --git a/x-pack/plugins/fleet/cypress.config.ts b/x-pack/plugins/fleet/cypress.config.ts index e4a5ad96938d6..2082142e23d7f 100644 --- a/x-pack/plugins/fleet/cypress.config.ts +++ b/x-pack/plugins/fleet/cypress.config.ts @@ -39,6 +39,7 @@ export default defineCypressConfig({ specPattern: './cypress/e2e/**/*.cy.ts', supportFile: './cypress/support/e2e.ts', + excludeSpecPattern: './cypress/e2e/space_awareness/**/*.cy.ts', setupNodeEvents(on, config) { // eslint-disable-next-line @typescript-eslint/no-var-requires, @kbn/imports/no_boundary_crossing diff --git a/x-pack/plugins/fleet/cypress/e2e/space_awareness/policies.cy.ts b/x-pack/plugins/fleet/cypress/e2e/space_awareness/policies.cy.ts new file mode 100644 index 0000000000000..8975de388248c --- /dev/null +++ b/x-pack/plugins/fleet/cypress/e2e/space_awareness/policies.cy.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 { + ADD_AGENT_POLICY_BTN, + AGENT_POLICIES_TABLE, + AGENT_POLICY_CREATE_AGENT_POLICY_NAME_FIELD, + AGENT_POLICY_DETAILS_PAGE, + AGENT_POLICY_FLYOUT_CREATE_BUTTON, + AGENT_POLICY_SYSTEM_MONITORING_CHECKBOX, +} from '../../screens/fleet'; +import { login } from '../../tasks/login'; +import { createSpaces, enableSpaceAwareness } from '../../tasks/spaces'; +import { cleanupAgentPolicies } from '../../tasks/cleanup'; + +describe('Space aware policies creation', { testIsolation: false }, () => { + before(() => { + enableSpaceAwareness(); + createSpaces(); + cleanupAgentPolicies(); + cleanupAgentPolicies('test'); + login(); + }); + + beforeEach(() => { + cy.intercept('GET', /\/api\/fleet\/agent_policies/).as('getAgentPolicies'); + cy.intercept('GET', /\/internal\/fleet\/agent_policies_spaces/).as('getAgentPoliciesSpaces'); + }); + + const POLICY_NAME = `Policy 1 space test`; + const NO_AGENT_POLICIES = 'No agent policies'; + it('should allow to create an agent policy in the test space', () => { + cy.visit('/s/test/app/fleet/policies'); + + cy.getBySel(ADD_AGENT_POLICY_BTN).click(); + cy.getBySel(AGENT_POLICY_CREATE_AGENT_POLICY_NAME_FIELD).type(POLICY_NAME); + cy.getBySel(AGENT_POLICY_SYSTEM_MONITORING_CHECKBOX).uncheck(); + + cy.getBySel(AGENT_POLICY_FLYOUT_CREATE_BUTTON).click(); + cy.getBySel(AGENT_POLICIES_TABLE).contains(POLICY_NAME); + }); + + it('the created policy should not be visible in the default space', () => { + cy.visit('/app/fleet/policies'); + cy.wait('@getAgentPolicies'); + cy.getBySel(AGENT_POLICIES_TABLE).contains(NO_AGENT_POLICIES); + }); + + it('should allow to update that policy to belong to both test and default space', () => { + cy.visit('/s/test/app/fleet/policies'); + cy.getBySel(AGENT_POLICIES_TABLE).contains(POLICY_NAME).click(); + + cy.getBySel(AGENT_POLICY_DETAILS_PAGE.SETTINGS_TAB).click(); + cy.wait('@getAgentPoliciesSpaces'); + cy.getBySel(AGENT_POLICY_DETAILS_PAGE.SPACE_SELECTOR_COMBOBOX).click().type('default{enter}'); + + cy.getBySel(AGENT_POLICY_DETAILS_PAGE.SAVE_BUTTON).click(); + }); + + it('the policy should be visible in the test space', () => { + cy.visit('/s/test/app/fleet/policies'); + cy.wait('@getAgentPolicies'); + cy.getBySel(AGENT_POLICIES_TABLE).contains(POLICY_NAME); + }); + + it('the policy should be visible in the default space', () => { + cy.visit('/app/fleet/policies'); + cy.wait('@getAgentPolicies'); + cy.getBySel(AGENT_POLICIES_TABLE).contains(POLICY_NAME); + }); +}); diff --git a/x-pack/plugins/fleet/cypress/screens/fleet.ts b/x-pack/plugins/fleet/cypress/screens/fleet.ts index 4e1a0ac0f7e19..0bd449652a800 100644 --- a/x-pack/plugins/fleet/cypress/screens/fleet.ts +++ b/x-pack/plugins/fleet/cypress/screens/fleet.ts @@ -46,6 +46,8 @@ export const AGENT_POLICY_CREATE_AGENT_POLICY_NAME_FIELD = 'createAgentPolicyNam export const AGENT_POLICIES_FLYOUT_ADVANCED_DEFAULT_NAMESPACE_HEADER = 'defaultNamespaceHeader'; export const AGENT_POLICY_FLYOUT_CREATE_BUTTON = 'createAgentPolicyFlyoutBtn'; +export const AGENT_POLICIES_TABLE = 'agentPoliciesTable'; + export const ENROLLMENT_TOKENS = { CREATE_TOKEN_BUTTON: 'createEnrollmentTokenButton', CREATE_TOKEN_MODAL_NAME_FIELD: 'createEnrollmentTokenNameField', @@ -241,4 +243,7 @@ export const API_KEYS = { export const AGENT_POLICY_DETAILS_PAGE = { ADD_AGENT_LINK: 'addAgentLink', + SETTINGS_TAB: 'agentPolicySettingsTab', + SPACE_SELECTOR_COMBOBOX: 'spaceSelectorComboBox', + SAVE_BUTTON: 'agentPolicyDetailsSaveButton', }; diff --git a/x-pack/plugins/fleet/cypress/tasks/cleanup.ts b/x-pack/plugins/fleet/cypress/tasks/cleanup.ts index 5e179bc9207f1..bad8743b66bd1 100644 --- a/x-pack/plugins/fleet/cypress/tasks/cleanup.ts +++ b/x-pack/plugins/fleet/cypress/tasks/cleanup.ts @@ -7,18 +7,20 @@ import { request } from './common'; -export function cleanupAgentPolicies() { - request({ url: '/api/fleet/agent_policies' }).then((response: any) => { - response.body.items - .filter((policy: any) => policy.agents === 0) - .forEach((policy: any) => { - request({ - method: 'POST', - url: '/api/fleet/agent_policies/delete', - body: { agentPolicyId: policy.id }, +export function cleanupAgentPolicies(spaceId?: string) { + request({ url: `${spaceId ? `/s/${spaceId}` : ''}/api/fleet/agent_policies` }).then( + (response: any) => { + response.body.items + .filter((policy: any) => policy.agents === 0) + .forEach((policy: any) => { + request({ + method: 'POST', + url: `${spaceId ? `/s/${spaceId}` : ''}/api/fleet/agent_policies/delete`, + body: { agentPolicyId: policy.id }, + }); }); - }); - }); + } + ); } export function unenrollAgent() { diff --git a/x-pack/plugins/fleet/cypress/tasks/common.ts b/x-pack/plugins/fleet/cypress/tasks/common.ts index 250dea07f89d1..de6e117bac4cc 100644 --- a/x-pack/plugins/fleet/cypress/tasks/common.ts +++ b/x-pack/plugins/fleet/cypress/tasks/common.ts @@ -28,6 +28,12 @@ export const COMMON_API_HEADERS = Object.freeze({ 'Elastic-Api-Version': API_VERSIONS.public.v1, }); +export const COMMON_INTERNAL_API_HEADERS = Object.freeze({ + 'kbn-xsrf': 'cypress', + 'x-elastic-internal-origin': 'fleet', + 'Elastic-Api-Version': API_VERSIONS.internal.v1, +}); + // Replaces request - adds baseline authentication + global headers export const request = ({ headers, @@ -40,6 +46,17 @@ export const request = ({ }); }; +export const internalRequest = ({ + headers, + ...options +}: Partial): Cypress.Chainable> => { + return cy.request({ + auth: API_AUTH, + headers: { ...COMMON_INTERNAL_API_HEADERS, ...headers }, + ...options, + }); +}; + /** * For all the new features tours we show in the app, this method disables them * by setting their configs in the local storage. It prevents the tours from appearing diff --git a/x-pack/plugins/fleet/cypress/tasks/spaces.ts b/x-pack/plugins/fleet/cypress/tasks/spaces.ts new file mode 100644 index 0000000000000..31c1d12a968cb --- /dev/null +++ b/x-pack/plugins/fleet/cypress/tasks/spaces.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { request, internalRequest } from './common'; + +export function enableSpaceAwareness() { + return internalRequest({ + url: '/internal/fleet/enable_space_awareness', + failOnStatusCode: false, + method: 'POST', + }); +} + +export function createSpaces() { + return request({ + url: '/api/spaces/space', + failOnStatusCode: false, + method: 'POST', + body: { + id: 'test', + name: 'Test', + description: 'Test space', + color: '#aabbcc', + initials: 'TE', + disabledFeatures: [], + imageUrl: + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABACAYAAABC6cT1AAAGf0lEQVRoQ+3abYydRRUH8N882xYo0IqagEVjokQJKAiKBjXExC9G/aCkGowCIghCkRcrVSSKIu/FEiqgGL6gBIlAYrAqUTH6hZgQFVEMKlQFfItWoQWhZe8z5uzMLdvbfbkLxb13d+fbvfe588x/zpn/+Z9zJpmnI81T3BaAzzfLL1h8weLzZAcWXH2eGHo7zAWLL1h8nuzAjFw9G1N6Kzq8HnuM36MR8iibF3Fv4q+7cv8yDV6K13bYq2furSP8Ag8ncr/vnSnwRViJT2GfCV7yL1yHGxLb+l3EdM9lluNEnIC9xz+f2ZL4Er6Z2DrdXN3fZwp8CU7OfDHxggle8lTLbQ1nJ/7Z7yKmey5zYGZt4h2IzR8/trRc2PDlxJPTzfVcgJ+CC0wMPOa9F6cm7up3EVM9V9386MxliVdM8GwAv6hh/awCz/w7lY25OtF5ruBz4ZLP42NYNrDAFbC3YPWuILnMAfgq3oaRQQYea/stViV+sgssvjKzLvGySeaaNVfP4d7Btokgvxj/bblgpueuF1hmWcyTCmfE3J3M1lTcv0vMswM88zR+jpw4osu6me8kzkpsfLZWzxyRuabO22buxxOJ12FxnXfWgEe83pB5sOE47BsLymzscOoi7nw2JJfZreUjiUsTyzKPZm5NvBDvSuw268AzNzV8H5/Am+qCnsAXgpgSW2Zq9cyKlksbPlTd+te4quWNieMHBfiNDdciYnwsdI/MaOaWhnMTf54J8CqNj8x8JXFIZltYu+HqlmNT8YSBsHgAPw/vxvlVV4du/s0oaxbxg0TbL/jMni0nNcVjQq7+HZfgtpbzBg342TgQ63AkmsymxBW4IjE6A+D7Vzd/fyWxIM/VuCe+HzTgZ2Jpy/kNJ2FJLmLm24mPJ/42A+Bvrxt4SISwlhsaPodH26LZB8rVA3inwwebsrixJCZzX+KMxI/7AV61eVh3DV6Mx3EOvh4kN6jAg8nfUCXm4d1wE66OyxNPTQc+s3/o/MoXizL3JE5O3F3P/uBZPPF4Zr+Wi5uSO48ZPRdyCwn7YB/A35m5KhWNHox4fcNnIs0ddOCRSBxf8+cQG+Huf0l8NJVYP+nI7NXy2ar4QqIGm69JfKPOE2w/mBavCzwM11R2D+ChsUO7hyUfmwx55qDM1xJvqZ7y08TpifuGBfjeURVJnNIVGpkNiXNS0ds7jcySDitDCCWW56LJ10fRo8sNA+3qXUSZD2CtQlZh9T+1rB7h9oliembflnMbzqgSNZKbKGHdPm7OwXb1CvQ1metSETMpszmzvikCJNh/h5E5PHNl4qga/+/cxqrdeWDYgIe7X5L4cGJPJX2940lOX8pD41FnFnc4riluvQKbK0dcHJFi2IBHNTQSlguru4d2/wPOTNzRA3x5y+U1E1uqWDkETOT026XuUJzx6u7ReLhSYenQ7uHua0fKZmwfmcPqsQjxE5WVONcRxn7X89zgn/EKPMRMxOVQXmP18Mx3q3b/Y/0cQE/IhFtHESMsHFlZ1Ml3CH3DZPHImY+pxcKumNmYirtvqMBfhMuU6s3iqOQkTsMPe1tCQwO8Ajs0lxr7W+vnp1MJc9EgCNd/cy6x+9D4veXmprj5wxMw/3C4egW6zzgZOlYZzfwo3F2J7ael0pJamvlPKgWNKFft1AAcKotXoFEbD7kaoSoQPVKB35+5KHF0lai/rJo+up87jWEE/qqqwY+qrL21LWLm95lPJ16ppKw31XC3PXYPJauPEx7B6BHCgrSizRs18qiaRp8tlN3ueCTYPHH9RNaunjI8Z7wLYpT3jZSCYXQ8e9vTsRE/q+no3XMKeObgGtaintbb/AvXj4JDkNw/5hrwYPfIvlZFUbLn7G5q+eQIN09Vnho6cqvnM/Lt99RixH49wO8K0ZL41WTWHoQzvsNVkOheZqKhEGpsp3SzB+BBtZAYve7uOR9tuTaaB6l0XScdYfEQPpkTUyHEGP+XqyDBzu+NBCITUjNWHynkrbWKOuWFn1xKzqsyx0bdvS78odp0+N503Zao0uCsWuSIDku8/7EO60b41vN5+Ses9BKlTdvd8bhp9EBvJjWJAIn/vxwHe6b3tSk6JFPV4nq85oAOrx555v/x/rh3E6Lo+bnuNS4uB4Cuq0ZfvO8X1rM6q/+vnjLVqZq7v83onttc2oYF4HPJmv1gWbB4P7s0l55ZsPhcsmY/WBYs3s8uzaVn5q3F/wf70mRuBCtbjQAAAABJRU5ErkJggg==', + }, + }).then((response: any) => { + if (response.status !== 200 && response.status !== 409) { + throw new Error(`Failed to create space test`); + } + }); +} diff --git a/x-pack/plugins/fleet/cypress/tsconfig.json b/x-pack/plugins/fleet/cypress/tsconfig.json index 6d1433482b1c2..5427996c27f17 100644 --- a/x-pack/plugins/fleet/cypress/tsconfig.json +++ b/x-pack/plugins/fleet/cypress/tsconfig.json @@ -3,6 +3,7 @@ "include": [ "**/*", "../cypress.config.ts", + "../cypress.config.space_awareness.ts", "../../../../typings/**/*" ], "exclude": [ diff --git a/x-pack/plugins/fleet/package.json b/x-pack/plugins/fleet/package.json index 3e20162ab1d91..dc0bc6a6bcacb 100644 --- a/x-pack/plugins/fleet/package.json +++ b/x-pack/plugins/fleet/package.json @@ -5,6 +5,10 @@ "private": true, "license": "Elastic License 2.0", "scripts": { + "cypress_space_awareness": "NODE_OPTIONS=--openssl-legacy-provider node ../security_solution/scripts/start_cypress_parallel --config-file ../fleet/cypress.config.space_awareness.ts --ftr-config-file ../../../x-pack/test/fleet_cypress/cli_config.space_awareness", + "cypress_space_awareness:open": "yarn cypress_space_awareness open", + "cypress_space_awareness:run": "yarn cypress_space_awareness run", + "cypress_space_awareness:run:reporter": "yarn cypress_space_awareness run --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=../fleet/cypress/reporter_config.json", "cypress": "NODE_OPTIONS=--openssl-legacy-provider node ../security_solution/scripts/start_cypress_parallel --config-file ../fleet/cypress.config.ts --ftr-config-file ../../../x-pack/test/fleet_cypress/cli_config", "cypress:open": "yarn cypress open", "cypress:run": "yarn cypress run", 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 14dcf6df21b9f..6e4f1e06b45a0 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 @@ -244,6 +244,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( Object.keys(validation).length > 0 || hasAdvancedSettingsErrors } + data-test-subj="agentPolicyDetailsSaveButton" iconType="save" color="primary" fill diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx index 0643ac82634d9..7f4c1a3b91ead 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx @@ -90,6 +90,7 @@ export const AgentPolicyDetailsPage: React.FunctionComponent = () => { name: i18n.translate('xpack.fleet.policyDetails.subTabs.settingsTabText', { defaultMessage: 'Settings', }), + 'data-test-subj': 'agentPolicySettingsTab', href: getHref('policy_details', { policyId, tabId: 'settings' }), isSelected: tabId === 'settings', }, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx index dcbd29c1ef74e..2682a5239071d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx @@ -356,6 +356,7 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => { loading={isLoading} + data-test-subj="agentPoliciesTable" noItemsMessage={ isLoading ? ( { ); }); + it('should delete agentless agent for ESS', async () => { + const returnValue = { + id: 'mocked', + }; + + (axios as jest.MockedFunction).mockResolvedValueOnce(returnValue); + jest.spyOn(appContextService, 'getConfig').mockReturnValue({ + agentless: { + enabled: true, + api: { + url: 'http://api.agentless.com', + tls: { + certificate: '/path/to/cert', + key: '/path/to/key', + ca: '/path/to/ca', + }, + }, + }, + } as any); + jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any); + + const deleteAgentlessAgentReturnValue = await agentlessAgentService.deleteAgentlessAgent( + 'mocked-agentless-agent-policy-id' + ); + + expect(axios).toHaveBeenCalledTimes(1); + expect(deleteAgentlessAgentReturnValue).toEqual(returnValue); + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.anything(), + httpsAgent: expect.anything(), + method: 'DELETE', + url: 'http://api.agentless.com/api/v1/ess/deployments/mocked-agentless-agent-policy-id', + }) + ); + }); + + it('should delete agentless agent for serverless', async () => { + const returnValue = { + id: 'mocked', + }; + + (axios as jest.MockedFunction).mockResolvedValueOnce(returnValue); + jest.spyOn(appContextService, 'getConfig').mockReturnValue({ + agentless: { + enabled: true, + api: { + url: 'http://api.agentless.com', + tls: { + certificate: '/path/to/cert', + key: '/path/to/key', + ca: '/path/to/ca', + }, + }, + }, + } as any); + jest + .spyOn(appContextService, 'getCloud') + .mockReturnValue({ isCloudEnabled: true, isServerlessEnabled: true } as any); + + const deleteAgentlessAgentReturnValue = await agentlessAgentService.deleteAgentlessAgent( + 'mocked-agentless-agent-policy-id' + ); + + expect(axios).toHaveBeenCalledTimes(1); + expect(deleteAgentlessAgentReturnValue).toEqual(returnValue); + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.anything(), + httpsAgent: expect.anything(), + method: 'DELETE', + url: 'http://api.agentless.com/api/v1/serverless/deployments/mocked-agentless-agent-policy-id', + }) + ); + }); + it('should redact sensitive information from debug logs', async () => { const returnValue = { id: 'mocked', diff --git a/x-pack/plugins/fleet/server/services/agents/agentless_agent.ts b/x-pack/plugins/fleet/server/services/agents/agentless_agent.ts index 3bf21c3bec0d1..617f3db7849f4 100644 --- a/x-pack/plugins/fleet/server/services/agents/agentless_agent.ts +++ b/x-pack/plugins/fleet/server/services/agents/agentless_agent.ts @@ -25,11 +25,7 @@ import { appContextService } from '../app_context'; import { listEnrollmentApiKeys } from '../api_keys'; import { listFleetServerHosts } from '../fleet_server_host'; import type { AgentlessConfig } from '../utils/agentless'; -import { - prependAgentlessApiBasePathToEndpoint, - isAgentlessApiEnabled, - getDeletionEndpointPath, -} from '../utils/agentless'; +import { prependAgentlessApiBasePathToEndpoint, isAgentlessApiEnabled } from '../utils/agentless'; class AgentlessAgentService { public async createAgentlessAgent( @@ -188,7 +184,10 @@ class AgentlessAgentService { const agentlessConfig = appContextService.getConfig()?.agentless; const tlsConfig = this.createTlsConfig(agentlessConfig); const requestConfig = { - url: getDeletionEndpointPath(agentlessConfig, `/deployments/${agentlessPolicyId}`), + url: prependAgentlessApiBasePathToEndpoint( + agentlessConfig, + `/deployments/${agentlessPolicyId}` + ), method: 'DELETE', headers: { 'Content-type': 'application/json', diff --git a/x-pack/plugins/fleet/server/services/utils/agentless.ts b/x-pack/plugins/fleet/server/services/utils/agentless.ts index c85e9cc991a6c..4c27d583d9a79 100644 --- a/x-pack/plugins/fleet/server/services/utils/agentless.ts +++ b/x-pack/plugins/fleet/server/services/utils/agentless.ts @@ -50,10 +50,3 @@ export const prependAgentlessApiBasePathToEndpoint = ( : AGENTLESS_ESS_API_BASE_PATH; return `${agentlessConfig.api.url}${endpointPrefix}${endpoint}`; }; - -export const getDeletionEndpointPath = ( - agentlessConfig: FleetConfigType['agentless'], - endpoint: AgentlessApiEndpoints -) => { - return `${agentlessConfig.api.url}${AGENTLESS_ESS_API_BASE_PATH}${endpoint}`; -}; diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index 7f7fc8e64829e..7ae2402aa6cb6 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -5,6 +5,7 @@ }, "exclude": [ "cypress.config.ts", + "cypress.config.space_awareness.ts", "target/**/*", ], "include": [ @@ -17,6 +18,7 @@ "scripts/**/*", "package.json", "cypress.config.ts", + "cypress.config.space_awareness.ts", "../../../typings/**/*" ], "kbn_references": [ diff --git a/x-pack/plugins/index_lifecycle_management/server/plugin.ts b/x-pack/plugins/index_lifecycle_management/server/plugin.ts index 0d88acbaaa4ff..a5002cd36da44 100644 --- a/x-pack/plugins/index_lifecycle_management/server/plugin.ts +++ b/x-pack/plugins/index_lifecycle_management/server/plugin.ts @@ -27,7 +27,6 @@ const indexLifecycleDataEnricher = async ( const { indices: ilmIndicesData } = await client.asCurrentUser.ilm.explainLifecycle({ index: '*,.*', - only_managed: true, }); return indicesList.map((index: Index) => { return { diff --git a/x-pack/plugins/lens/common/expressions/datatable/utils.ts b/x-pack/plugins/lens/common/expressions/datatable/utils.ts index 71c3d92126b33..bc617d931f500 100644 --- a/x-pack/plugins/lens/common/expressions/datatable/utils.ts +++ b/x-pack/plugins/lens/common/expressions/datatable/utils.ts @@ -5,14 +5,37 @@ * 2.0. */ -import type { Datatable } from '@kbn/expressions-plugin/common'; +import { type Datatable, type DatatableColumnMeta } from '@kbn/expressions-plugin/common'; import { getOriginalId } from './transpose_helpers'; +/** + * Returns true for numerical fields + * + * Excludes the following types: + * - `range` - Stringified range + * - `multi_terms` - Multiple values + * - `filters` - Arbitrary label + * - `filtered_metric` - Array of values + */ +export function isNumericField(meta?: DatatableColumnMeta): boolean { + return ( + meta?.type === 'number' && + meta.params?.id !== 'range' && + meta.params?.id !== 'multi_terms' && + meta.sourceParams?.type !== 'filters' && + meta.sourceParams?.type !== 'filtered_metric' + ); +} + +/** + * Returns true for numerical fields, excluding ranges + */ export function isNumericFieldForDatatable(table: Datatable | undefined, accessor: string) { - return getFieldTypeFromDatatable(table, accessor) === 'number'; + const meta = getFieldMetaFromDatatable(table, accessor); + return isNumericField(meta); } -export function getFieldTypeFromDatatable(table: Datatable | undefined, accessor: string) { +export function getFieldMetaFromDatatable(table: Datatable | undefined, accessor: string) { return table?.columns.find((col) => col.id === accessor || getOriginalId(col.id) === accessor) - ?.meta.type; + ?.meta; } diff --git a/x-pack/plugins/lens/public/shared_components/coloring/utils.test.ts b/x-pack/plugins/lens/public/shared_components/coloring/utils.test.ts index 5a126565c251f..cc6044fc0f624 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/utils.test.ts +++ b/x-pack/plugins/lens/public/shared_components/coloring/utils.test.ts @@ -110,7 +110,7 @@ describe('findMinMaxByColumnId', () => { { a: 'shoes', b: 53 }, ], }) - ).toEqual({ b: { min: 2, max: 53 } }); + ).toEqual(new Map([['b', { min: 2, max: 53 }]])); }); }); diff --git a/x-pack/plugins/lens/public/shared_components/coloring/utils.ts b/x-pack/plugins/lens/public/shared_components/coloring/utils.ts index 211628a096189..c58fec1ddb03e 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/utils.ts +++ b/x-pack/plugins/lens/public/shared_components/coloring/utils.ts @@ -95,12 +95,12 @@ export const findMinMaxByColumnId = ( table: Datatable | undefined, getOriginalId: (id: string) => string = (id: string) => id ) => { - const minMax: Record = {}; + const minMaxMap = new Map(); if (table != null) { for (const columnId of columnIds) { const originalId = getOriginalId(columnId); - minMax[originalId] = minMax[originalId] || { + const minMax = minMaxMap.get(originalId) ?? { max: Number.NEGATIVE_INFINITY, min: Number.POSITIVE_INFINITY, }; @@ -108,19 +108,22 @@ export const findMinMaxByColumnId = ( const rowValue = row[columnId]; const numericValue = getNumericValue(rowValue); if (numericValue != null) { - if (minMax[originalId].min > numericValue) { - minMax[originalId].min = numericValue; + if (minMax.min > numericValue) { + minMax.min = numericValue; } - if (minMax[originalId].max < numericValue) { - minMax[originalId].max = numericValue; + if (minMax.max < numericValue) { + minMax.max = numericValue; } } }); + // what happens when there's no data in the table? Fallback to a percent range - if (minMax[originalId].max === Number.NEGATIVE_INFINITY) { - minMax[originalId] = getFallbackDataBounds(); + if (minMax.max === Number.NEGATIVE_INFINITY) { + minMaxMap.set(originalId, getFallbackDataBounds()); + } else { + minMaxMap.set(originalId, minMax); } } } - return minMax; + return minMaxMap; }; diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/cell_value.test.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/cell_value.test.tsx index 76b8fc7b61740..e9f3caba9ec05 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/cell_value.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/cell_value.test.tsx @@ -54,9 +54,7 @@ describe('datatable cell renderer', () => { @@ -217,7 +215,7 @@ describe('datatable cell renderer', () => { { wrapper: DataContextProviderWrapper({ table, - minMaxByColumnId: { a: { min: 12, max: 155 } }, + minMaxByColumnId: new Map([['a', { min: 12, max: 155 }]]), ...context, }), } diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/cell_value.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/cell_value.tsx index 0761c7904e75f..97e7e755ac36e 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/cell_value.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/cell_value.tsx @@ -53,7 +53,7 @@ export const createGridCell = ( } = columnConfig.columns[colIndex] ?? {}; const filterOnClick = oneClickFilter && handleFilterClick; const content = formatters[columnId]?.convert(rawRowValue, filterOnClick ? 'text' : 'html'); - const currentAlignment = alignments && alignments[columnId]; + const currentAlignment = alignments?.get(columnId); useEffect(() => { let colorSet = false; diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/columns.test.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/columns.test.tsx index 3612317f7a565..76437743c5723 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/columns.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/columns.test.tsx @@ -72,7 +72,7 @@ const callCreateGridColumns = ( params.formatFactory ?? (((x: unknown) => ({ convert: () => x })) as unknown as FormatFactory), params.onColumnResize ?? jest.fn(), params.onColumnHide ?? jest.fn(), - params.alignments ?? {}, + params.alignments ?? new Map(), params.headerRowHeight ?? RowHeightMode.auto, params.headerRowLines ?? 1, params.columnCellValueActions ?? [], diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/columns.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/columns.tsx index 6cd8c32db4b6d..8d2fcc9fac0c0 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/columns.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/columns.tsx @@ -51,7 +51,7 @@ export const createGridColumns = ( formatFactory: FormatFactory, onColumnResize: (eventData: { columnId: string; width: number | undefined }) => void, onColumnHide: ((eventData: { columnId: string }) => void) | undefined, - alignments: Record, + alignments: Map, headerRowHeight: RowHeightMode, headerRowLines: number, columnCellValueActions: LensCellValueAction[][] | undefined, @@ -261,7 +261,7 @@ export const createGridColumns = ( }); } } - const currentAlignment = alignments && alignments[field]; + const currentAlignment = alignments && alignments.get(field); const hasMultipleRows = [RowHeightMode.auto, RowHeightMode.custom, undefined].includes( headerRowHeight ); diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.test.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.test.tsx index 09c7d95b309e7..738f7edab2a6e 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.test.tsx @@ -6,25 +6,20 @@ */ import React from 'react'; -import { DEFAULT_COLOR_MAPPING_CONFIG, type PaletteRegistry } from '@kbn/coloring'; +import { DEFAULT_COLOR_MAPPING_CONFIG } from '@kbn/coloring'; import { act, render, screen } from '@testing-library/react'; import userEvent, { type UserEvent } from '@testing-library/user-event'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { EuiButtonGroupTestHarness } from '@kbn/test-eui-helpers'; -import { - FramePublicAPI, - OperationDescriptor, - VisualizationDimensionEditorProps, - DatasourcePublicAPI, - DataType, -} from '../../../types'; +import { FramePublicAPI, DatasourcePublicAPI, OperationDescriptor } from '../../../types'; import { DatatableVisualizationState } from '../visualization'; import { createMockDatasource, createMockFramePublicAPI } from '../../../mocks'; -import { TableDimensionEditor } from './dimension_editor'; +import { TableDimensionEditor, TableDimensionEditorProps } from './dimension_editor'; import { ColumnState } from '../../../../common/expressions'; import { capitalize } from 'lodash'; import { I18nProvider } from '@kbn/i18n-react'; +import { DatatableColumnType } from '@kbn/expressions-plugin/common'; describe('data table dimension editor', () => { let user: UserEvent; @@ -35,10 +30,8 @@ describe('data table dimension editor', () => { alignment: EuiButtonGroupTestHarness; }; let mockOperationForFirstColumn: (overrides?: Partial) => void; - let props: VisualizationDimensionEditorProps & { - paletteService: PaletteRegistry; - isDarkMode: boolean; - }; + + let props: TableDimensionEditorProps; function testState(): DatatableVisualizationState { return { @@ -80,6 +73,7 @@ describe('data table dimension editor', () => { name: 'foo', meta: { type: 'string', + params: {}, }, }, ], @@ -114,13 +108,7 @@ describe('data table dimension editor', () => { mockOperationForFirstColumn(); }); - const renderTableDimensionEditor = ( - overrideProps?: Partial< - VisualizationDimensionEditorProps & { - paletteService: PaletteRegistry; - } - > - ) => { + const renderTableDimensionEditor = (overrideProps?: Partial) => { return render(, { wrapper: ({ children }) => ( @@ -137,11 +125,18 @@ describe('data table dimension editor', () => { }); it('should render default alignment for number', () => { - mockOperationForFirstColumn({ dataType: 'number' }); + frame.activeData!.first.columns[0].meta.type = 'number'; renderTableDimensionEditor(); expect(btnGroups.alignment.selected).toHaveTextContent('Right'); }); + it('should render default alignment for ranges', () => { + frame.activeData!.first.columns[0].meta.type = 'number'; + frame.activeData!.first.columns[0].meta.params = { id: 'range' }; + renderTableDimensionEditor(); + expect(btnGroups.alignment.selected).toHaveTextContent('Left'); + }); + it('should render specific alignment', () => { state.columns[0].alignment = 'center'; renderTableDimensionEditor(); @@ -181,10 +176,11 @@ describe('data table dimension editor', () => { expect(screen.queryByTestId('lns_dynamicColoring_edit')).not.toBeInTheDocument(); }); - it.each(['date'])( + it.each(['date'])( 'should not show the dynamic coloring option for "%s" columns', - (dataType) => { - mockOperationForFirstColumn({ dataType }); + (type) => { + frame.activeData!.first.columns[0].meta.type = type; + renderTableDimensionEditor(); expect(screen.queryByTestId('lnsDatatable_dynamicColoring_groups')).not.toBeInTheDocument(); expect(screen.queryByTestId('lns_dynamicColoring_edit')).not.toBeInTheDocument(); @@ -231,15 +227,16 @@ describe('data table dimension editor', () => { }); }); - it.each<{ flyout: 'terms' | 'values'; isBucketed: boolean; dataType: DataType }>([ - { flyout: 'terms', isBucketed: true, dataType: 'number' }, - { flyout: 'terms', isBucketed: false, dataType: 'string' }, - { flyout: 'values', isBucketed: false, dataType: 'number' }, + it.each<{ flyout: 'terms' | 'values'; isBucketed: boolean; type: DatatableColumnType }>([ + { flyout: 'terms', isBucketed: true, type: 'number' }, + { flyout: 'terms', isBucketed: false, type: 'string' }, + { flyout: 'values', isBucketed: false, type: 'number' }, ])( - 'should show color by $flyout flyout when bucketing is $isBucketed with $dataType column', - async ({ flyout, isBucketed, dataType }) => { + 'should show color by $flyout flyout when bucketing is $isBucketed with $type column', + async ({ flyout, isBucketed, type }) => { state.columns[0].colorMode = 'cell'; - mockOperationForFirstColumn({ isBucketed, dataType }); + frame.activeData!.first.columns[0].meta.type = type; + mockOperationForFirstColumn({ isBucketed }); renderTableDimensionEditor(); await user.click(screen.getByLabelText('Edit colors')); @@ -251,6 +248,7 @@ describe('data table dimension editor', () => { it('should show the dynamic coloring option for a bucketed operation', () => { state.columns[0].colorMode = 'cell'; + frame.activeData!.first.columns[0].meta.type = 'string'; mockOperationForFirstColumn({ isBucketed: true }); renderTableDimensionEditor(); diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx index c1e097276cf3d..99fe3cc1c164e 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/dimension_editor.tsx @@ -8,7 +8,7 @@ import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiSwitch, EuiButtonGroup, htmlIdGenerator } from '@elastic/eui'; -import { PaletteRegistry } from '@kbn/coloring'; +import { PaletteRegistry, getFallbackDataBounds } from '@kbn/coloring'; import { getColorCategories } from '@kbn/chart-expressions-common'; import { useDebouncedValue } from '@kbn/visualization-utils'; import type { VisualizationDimensionEditorProps } from '../../../types'; @@ -26,6 +26,11 @@ import './dimension_editor.scss'; import { CollapseSetting } from '../../../shared_components/collapse_setting'; import { ColorMappingByValues } from '../../../shared_components/coloring/color_mapping_by_values'; import { ColorMappingByTerms } from '../../../shared_components/coloring/color_mapping_by_terms'; +import { getColumnAlignment } from '../utils'; +import { + getFieldMetaFromDatatable, + isNumericField, +} from '../../../../common/expressions/datatable/utils'; const idPrefix = htmlIdGenerator()(); @@ -45,12 +50,13 @@ function updateColumn( }); } -export function TableDimensionEditor( - props: VisualizationDimensionEditorProps & { +export type TableDimensionEditorProps = + VisualizationDimensionEditorProps & { paletteService: PaletteRegistry; isDarkMode: boolean; - } -) { + }; + +export function TableDimensionEditor(props: TableDimensionEditorProps) { const { frame, accessor, isInlineEditing, isDarkMode } = props; const column = props.state.columns.find(({ columnId }) => accessor === columnId); const { inputValue: localState, handleInputChange: setLocalState } = @@ -74,12 +80,13 @@ export function TableDimensionEditor( const currentData = frame.activeData?.[localState.layerId]; const datasource = frame.datasourceLayers?.[localState.layerId]; - const { dataType, isBucketed } = datasource?.getOperationForColumnId(accessor) ?? {}; - const showColorByTerms = shouldColorByTerms(dataType, isBucketed); - const currentAlignment = column?.alignment || (dataType === 'number' ? 'right' : 'left'); + const { isBucketed } = datasource?.getOperationForColumnId(accessor) ?? {}; + const meta = getFieldMetaFromDatatable(currentData, accessor); + const showColorByTerms = shouldColorByTerms(meta?.type, isBucketed); + const currentAlignment = getColumnAlignment(column, isNumericField(meta)); const currentColorMode = column?.colorMode || 'none'; const hasDynamicColoring = currentColorMode !== 'none'; - const showDynamicColoringFeature = dataType !== 'date'; + const showDynamicColoringFeature = meta?.type !== 'date'; const visibleColumnsCount = localState.columns.filter((c) => !c.hidden).length; const hasTransposedColumn = localState.columns.some(({ isTransposed }) => isTransposed); @@ -88,7 +95,7 @@ export function TableDimensionEditor( [] : [accessor]; const minMaxByColumnId = findMinMaxByColumnId(columnsToCheck, currentData, getOriginalId); - const currentMinMax = minMaxByColumnId[accessor]; + const currentMinMax = minMaxByColumnId.get(accessor) ?? getFallbackDataBounds(); const activePalette = column?.palette ?? { type: 'palette', diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.test.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.test.tsx index 21361f874e83e..2358b9ec5b563 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.test.tsx @@ -11,7 +11,6 @@ import userEvent from '@testing-library/user-event'; import { I18nProvider } from '@kbn/i18n-react'; import faker from 'faker'; import { act } from 'react-dom/test-utils'; -import { IAggType } from '@kbn/data-plugin/public'; import { IFieldFormat } from '@kbn/field-formats-plugin/common'; import { coreMock } from '@kbn/core/public/mocks'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; @@ -73,6 +72,17 @@ function sampleArgs() { sourceParams: { indexPatternId, type: 'count' }, }, }, + { + id: 'd', + name: 'd', + meta: { + type: 'number', + source: 'esaggs', + field: 'd', + params: { id: 'range' }, + sourceParams: { indexPatternId, type: 'range' }, + }, + }, ], rows: [{ a: 'shoes', b: 1588024800000, c: 3 }], }; @@ -119,7 +129,9 @@ describe('DatatableComponent', () => { args, formatFactory: () => ({ convert: (x) => x } as IFieldFormat), dispatchEvent: onDispatchEvent, - getType: jest.fn(() => ({ type: 'buckets' } as IAggType)), + getType: jest.fn().mockReturnValue({ + type: 'buckets', + }), paletteService: chartPluginMock.createPaletteRegistry(), theme: setUpMockTheme, renderMode: 'edit' as const, @@ -357,14 +369,39 @@ describe('DatatableComponent', () => { ]); }); - test('it adds alignment data to context', () => { + test('it adds explicit alignment to context', () => { renderDatatableComponent({ args: { ...args, columns: [ { columnId: 'a', alignment: 'center', type: 'lens_datatable_column', colorMode: 'none' }, + { columnId: 'b', alignment: 'center', type: 'lens_datatable_column', colorMode: 'none' }, + { columnId: 'c', alignment: 'center', type: 'lens_datatable_column', colorMode: 'none' }, + { columnId: 'd', alignment: 'center', type: 'lens_datatable_column', colorMode: 'none' }, + ], + }, + }); + const alignmentsClassNames = screen + .getAllByTestId('lnsTableCellContent') + .map((cell) => cell.className); + + expect(alignmentsClassNames).toEqual([ + 'lnsTableCell--center', // set via args + 'lnsTableCell--center', // set via args + 'lnsTableCell--center', // set via args + 'lnsTableCell--center', // set via args + ]); + }); + + test('it adds default alignment data to context', () => { + renderDatatableComponent({ + args: { + ...args, + columns: [ + { columnId: 'a', type: 'lens_datatable_column', colorMode: 'none' }, { columnId: 'b', type: 'lens_datatable_column', colorMode: 'none' }, { columnId: 'c', type: 'lens_datatable_column', colorMode: 'none' }, + { columnId: 'd', type: 'lens_datatable_column', colorMode: 'none' }, ], sortingColumnId: 'b', sortingDirection: 'desc', @@ -375,9 +412,10 @@ describe('DatatableComponent', () => { .map((cell) => cell.className); expect(alignmentsClassNames).toEqual([ - 'lnsTableCell--center', // set via args + 'lnsTableCell--left', // default for string 'lnsTableCell--left', // default for date 'lnsTableCell--right', // default for number + 'lnsTableCell--left', // default for range ]); }); diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx index 83249f86ffa79..55e198b943e81 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx @@ -6,7 +6,7 @@ */ import './table_basic.scss'; -import { ColorMappingInputData, PaletteOutput } from '@kbn/coloring'; +import { ColorMappingInputData, PaletteOutput, getFallbackDataBounds } from '@kbn/coloring'; import React, { useLayoutEffect, useCallback, @@ -58,8 +58,12 @@ import { } from './table_actions'; import { getFinalSummaryConfiguration } from '../../../../common/expressions/datatable/summary'; import { DEFAULT_HEADER_ROW_HEIGHT, DEFAULT_HEADER_ROW_HEIGHT_LINES } from './constants'; -import { getFieldTypeFromDatatable } from '../../../../common/expressions/datatable/utils'; +import { + getFieldMetaFromDatatable, + isNumericField, +} from '../../../../common/expressions/datatable/utils'; import { CellColorFn, getCellColorFn } from '../../../shared_components/coloring/get_cell_color_fn'; +import { getColumnAlignment } from '../utils'; export const DataContext = React.createContext({}); @@ -229,10 +233,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => { columnConfig.columns .filter((_col, index) => { const col = firstTableRef.current.columns[index]; - return ( - col?.meta?.sourceParams?.type && - getType(col.meta.sourceParams.type as string)?.type === 'buckets' - ); + return getType(col?.meta)?.type === 'buckets'; }) .map((col) => col.columnId), [firstTableRef, columnConfig, getType] @@ -240,7 +241,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => { const isEmpty = firstLocalTable.rows.length === 0 || - (bucketedColumns.length && + (bucketedColumns.length > 0 && props.data.rows.every((row) => bucketedColumns.every((col) => row[col] == null))); const visibleColumns = useMemo( @@ -266,34 +267,26 @@ export const DatatableComponent = (props: DatatableRenderProps) => { [onEditAction, setColumnConfig, columnConfig, isInteractive] ); - const isNumericMap: Record = useMemo( + const isNumericMap: Map = useMemo( () => - firstLocalTable.columns.reduce>( - (map, column) => ({ - ...map, - [column.id]: column.meta.type === 'number', - }), - {} - ), - [firstLocalTable] + firstLocalTable.columns.reduce((acc, column) => { + acc.set(column.id, isNumericField(column.meta)); + return acc; + }, new Map()), + [firstLocalTable.columns] ); - const alignments: Record = useMemo(() => { - const alignmentMap: Record = {}; - columnConfig.columns.forEach((column) => { - if (column.alignment) { - alignmentMap[column.columnId] = column.alignment; - } else { - alignmentMap[column.columnId] = isNumericMap[column.columnId] ? 'right' : 'left'; - } - }); - return alignmentMap; - }, [columnConfig, isNumericMap]); + const alignments: Map = useMemo(() => { + return columnConfig.columns.reduce((acc, column) => { + acc.set(column.columnId, getColumnAlignment(column, isNumericMap.get(column.columnId))); + return acc; + }, new Map()); + }, [columnConfig.columns, isNumericMap]); - const minMaxByColumnId: Record = useMemo(() => { + const minMaxByColumnId: Map = useMemo(() => { return findMinMaxByColumnId( columnConfig.columns - .filter(({ columnId }) => isNumericMap[columnId]) + .filter(({ columnId }) => isNumericMap.get(columnId)) .map(({ columnId }) => columnId), props.data, getOriginalId @@ -402,7 +395,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => { return cellColorFnMap.get(originalId)!; } - const dataType = getFieldTypeFromDatatable(firstLocalTable, originalId); + const dataType = getFieldMetaFromDatatable(firstLocalTable, originalId)?.type; const isBucketed = bucketedColumns.some((id) => id === columnId); const colorByTerms = shouldColorByTerms(dataType, isBucketed); @@ -419,7 +412,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => { : { type: 'ranges', bins: 0, - ...minMaxByColumnId[originalId], + ...(minMaxByColumnId.get(originalId) ?? getFallbackDataBounds()), }; const colorFn = getCellColorFn( props.paletteService, @@ -491,7 +484,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => { ]) ); return ({ columnId }: { columnId: string }) => { - const currentAlignment = alignments && alignments[columnId]; + const currentAlignment = alignments.get(columnId); const alignmentClassName = `lnsTableCell--${currentAlignment}`; const columnName = columns.find(({ id }) => id === columnId)?.displayAsText?.replace(/ /g, '-') || columnId; diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/types.ts b/x-pack/plugins/lens/public/visualizations/datatable/components/types.ts index b884a2c716be9..00d916bf956ae 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/types.ts +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/types.ts @@ -8,7 +8,7 @@ import { CoreSetup } from '@kbn/core/public'; import type { PaletteRegistry } from '@kbn/coloring'; import type { IAggType } from '@kbn/data-plugin/public'; -import type { Datatable, RenderMode } from '@kbn/expressions-plugin/common'; +import type { Datatable, DatatableColumnMeta, RenderMode } from '@kbn/expressions-plugin/common'; import type { ILensInterpreterRenderHandlers, LensCellValueAction, @@ -49,7 +49,7 @@ export type LensPagesizeAction = LensEditEvent export type DatatableRenderProps = DatatableProps & { formatFactory: FormatFactory; dispatchEvent: ILensInterpreterRenderHandlers['event']; - getType: (name: string) => IAggType | undefined; + getType: (meta?: DatatableColumnMeta) => IAggType | undefined; renderMode: RenderMode; paletteService: PaletteRegistry; theme: CoreSetup['theme']; @@ -72,8 +72,8 @@ export type DatatableRenderProps = DatatableProps & { export interface DataContextType { table?: Datatable; rowHasRowClickTriggerActions?: boolean[]; - alignments?: Record; - minMaxByColumnId?: Record; + alignments?: Map; + minMaxByColumnId?: Map; handleFilterClick?: ( field: string, value: unknown, diff --git a/x-pack/plugins/lens/public/visualizations/datatable/expression.tsx b/x-pack/plugins/lens/public/visualizations/datatable/expression.tsx index 652abec75695e..a5927dd9183bf 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/expression.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/expression.tsx @@ -13,6 +13,7 @@ import type { IAggType } from '@kbn/data-plugin/public'; import { CoreSetup, IUiSettingsClient } from '@kbn/core/public'; import type { Datatable, + DatatableColumnMeta, ExpressionRenderDefinition, IInterpreterRenderHandlers, } from '@kbn/expressions-plugin/common'; @@ -102,6 +103,11 @@ export const getDatatableRenderer = (dependencies: { handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); const resolvedGetType = await dependencies.getType; + const getType = (meta?: DatatableColumnMeta): IAggType | undefined => { + if (meta?.sourceParams?.type === undefined) return; + return resolvedGetType(String(meta.sourceParams.type)); + }; + const { hasCompatibleActions, isInteractive, getCompatibleCellValueActions } = handlers; const renderComplete = () => { @@ -161,7 +167,7 @@ export const getDatatableRenderer = (dependencies: { dispatchEvent={handlers.event} renderMode={handlers.getRenderMode()} paletteService={dependencies.paletteService} - getType={resolvedGetType} + getType={getType} rowHasRowClickTriggerActions={rowHasRowClickTriggerActions} columnCellValueActions={columnCellValueActions} columnFilterable={columnsFilterable} diff --git a/x-pack/plugins/lens/public/visualizations/datatable/index.ts b/x-pack/plugins/lens/public/visualizations/datatable/index.ts index f68f167ea5f02..93e5e38e03c3c 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/index.ts +++ b/x-pack/plugins/lens/public/visualizations/datatable/index.ts @@ -32,6 +32,7 @@ export class DatatableVisualization { '../../async_services' ); const palettes = await charts.palettes.getPalettes(); + expressions.registerRenderer(() => getDatatableRenderer({ formatFactory, @@ -44,7 +45,10 @@ export class DatatableVisualization { }) ); - return getDatatableVisualization({ paletteService: palettes, kibanaTheme: core.theme }); + return getDatatableVisualization({ + paletteService: palettes, + kibanaTheme: core.theme, + }); }); } } diff --git a/x-pack/plugins/lens/public/visualizations/datatable/utils.ts b/x-pack/plugins/lens/public/visualizations/datatable/utils.ts new file mode 100644 index 0000000000000..ab4d8f05f8d44 --- /dev/null +++ b/x-pack/plugins/lens/public/visualizations/datatable/utils.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function getColumnAlignment( + { alignment }: C, + isNumeric = false +): 'left' | 'right' | 'center' { + if (alignment) return alignment; + return isNumeric ? 'right' : 'left'; +} diff --git a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx index 0187776985a30..d2d23b2033f90 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/visualization.tsx @@ -147,8 +147,8 @@ export const getDatatableVisualization = ({ .map(({ id }) => id) || [] : [accessor]; const minMaxByColumnId = findMinMaxByColumnId(columnsToCheck, currentData, getOriginalId); - - if (palette && !showColorByTerms && !palette?.canDynamicColoring) { + const dataBounds = minMaxByColumnId.get(accessor); + if (palette && !showColorByTerms && !palette?.canDynamicColoring && dataBounds) { const newPalette: PaletteOutput = { type: 'palette', name: showColorByTerms ? 'default' : defaultPaletteParams.name, @@ -158,7 +158,7 @@ export const getDatatableVisualization = ({ palette: { ...newPalette, params: { - stops: applyPaletteParams(paletteService, newPalette, minMaxByColumnId[accessor]), + stops: applyPaletteParams(paletteService, newPalette, dataBounds), }, }, }; diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/utils.ts b/x-pack/plugins/lens/public/visualizations/heatmap/utils.ts index 5e09ce2987bae..fe942dd40427c 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/utils.ts +++ b/x-pack/plugins/lens/public/visualizations/heatmap/utils.ts @@ -26,7 +26,10 @@ export function getSafePaletteParams( accessor, }; const minMaxByColumnId = findMinMaxByColumnId([accessor], currentData); - const currentMinMax = minMaxByColumnId[accessor]; + const currentMinMax = minMaxByColumnId.get(accessor) ?? { + max: Number.NEGATIVE_INFINITY, + min: Number.POSITIVE_INFINITY, + }; // need to tell the helper that the colorStops are required to display return { diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx index 4df52758ceda3..a1dadbf186b91 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx @@ -5,19 +5,36 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import moment from 'moment'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { LogStream } from '@kbn/logs-shared-plugin/public'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; -import { useFetcher } from '../../../hooks/use_fetcher'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; -import { APIReturnType } from '../../../services/rest/create_call_apm_api'; - import { CONTAINER_ID, SERVICE_ENVIRONMENT, SERVICE_NAME } from '../../../../common/es_fields/apm'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { useKibana } from '../../../context/kibana_context/use_kibana'; import { useAnyOfApmParams } from '../../../hooks/use_apm_params'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { useTimeRange } from '../../../hooks/use_time_range'; +import { APIReturnType } from '../../../services/rest/create_call_apm_api'; export function ServiceLogs() { + const { + services: { + logsShared: { LogsOverview }, + }, + } = useKibana(); + + const isLogsOverviewEnabled = LogsOverview.useIsEnabled(); + + if (isLogsOverviewEnabled) { + return ; + } else { + return ; + } +} + +export function ClassicServiceLogsStream() { const { serviceName } = useApmServiceContext(); const { @@ -58,6 +75,54 @@ export function ServiceLogs() { ); } +export function ServiceLogsOverview() { + const { + services: { logsShared }, + } = useKibana(); + const { serviceName } = useApmServiceContext(); + const { + query: { environment, kuery, rangeFrom, rangeTo }, + } = useAnyOfApmParams('/services/{serviceName}/logs'); + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const timeRange = useMemo(() => ({ start, end }), [start, end]); + + const { data: logFilters, status } = useFetcher( + async (callApmApi) => { + if (start == null || end == null) { + return; + } + + const { containerIds } = await callApmApi( + 'GET /internal/apm/services/{serviceName}/infrastructure_attributes', + { + params: { + path: { serviceName }, + query: { + environment, + kuery, + start, + end, + }, + }, + } + ); + + return [getInfrastructureFilter({ containerIds, environment, serviceName })]; + }, + [environment, kuery, serviceName, start, end] + ); + + if (status === FETCH_STATUS.SUCCESS) { + return ; + } else if (status === FETCH_STATUS.FAILURE) { + return ( + + ); + } else { + return ; + } +} + export function getInfrastructureKQLFilter({ data, serviceName, @@ -84,3 +149,99 @@ export function getInfrastructureKQLFilter({ return [serviceNameAndEnvironmentCorrelation, ...containerIdCorrelation].join(' or '); } + +export function getInfrastructureFilter({ + containerIds, + environment, + serviceName, +}: { + containerIds: string[]; + environment: string; + serviceName: string; +}): QueryDslQueryContainer { + return { + bool: { + should: [ + ...getServiceShouldClauses({ environment, serviceName }), + ...getContainerShouldClauses({ containerIds }), + ], + minimum_should_match: 1, + }, + }; +} + +export function getServiceShouldClauses({ + environment, + serviceName, +}: { + environment: string; + serviceName: string; +}): QueryDslQueryContainer[] { + const serviceNameFilter: QueryDslQueryContainer = { + term: { + [SERVICE_NAME]: serviceName, + }, + }; + + if (environment === ENVIRONMENT_ALL.value) { + return [serviceNameFilter]; + } else { + return [ + { + bool: { + filter: [ + serviceNameFilter, + { + term: { + [SERVICE_ENVIRONMENT]: environment, + }, + }, + ], + }, + }, + { + bool: { + filter: [serviceNameFilter], + must_not: [ + { + exists: { + field: SERVICE_ENVIRONMENT, + }, + }, + ], + }, + }, + ]; + } +} + +export function getContainerShouldClauses({ + containerIds = [], +}: { + containerIds: string[]; +}): QueryDslQueryContainer[] { + if (containerIds.length === 0) { + return []; + } + + return [ + { + bool: { + filter: [ + { + terms: { + [CONTAINER_ID]: containerIds, + }, + }, + ], + must_not: [ + { + term: { + [SERVICE_NAME]: '*', + }, + }, + ], + }, + }, + ]; +} diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/service_detail/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/service_detail/index.tsx index d746e0464fd40..8a4a1c32877c5 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/routing/service_detail/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/service_detail/index.tsx @@ -330,7 +330,7 @@ export const serviceDetailRoute = { }), element: , searchBarOptions: { - showUnifiedSearchBar: false, + showQueryInput: false, }, }), '/services/{serviceName}/infrastructure': { diff --git a/x-pack/plugins/observability_solution/apm/public/plugin.ts b/x-pack/plugins/observability_solution/apm/public/plugin.ts index 9a9f45f42a39e..b21bdedac9ef8 100644 --- a/x-pack/plugins/observability_solution/apm/public/plugin.ts +++ b/x-pack/plugins/observability_solution/apm/public/plugin.ts @@ -69,6 +69,7 @@ import { from } from 'rxjs'; import { map } from 'rxjs'; import type { CloudSetup } from '@kbn/cloud-plugin/public'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; +import { LogsSharedClientStartExports } from '@kbn/logs-shared-plugin/public'; import type { ConfigSchema } from '.'; import { registerApmRuleTypes } from './components/alerting/rule_types/register_apm_rule_types'; import { registerEmbeddables } from './embeddable/register_embeddables'; @@ -142,6 +143,7 @@ export interface ApmPluginStartDeps { dashboard: DashboardStart; metricsDataAccess: MetricsDataPluginStart; uiSettings: IUiSettingsClient; + logsShared: LogsSharedClientStartExports; } const applicationsTitle = i18n.translate('xpack.apm.navigation.rootTitle', { diff --git a/x-pack/plugins/observability_solution/infra/public/components/logging/log_analysis_setup/missing_results_privileges_prompt.tsx b/x-pack/plugins/observability_solution/infra/public/components/logging/log_analysis_setup/missing_results_privileges_prompt.tsx index 97eeeabe8721b..dce819ffb0930 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/logging/log_analysis_setup/missing_results_privileges_prompt.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/logging/log_analysis_setup/missing_results_privileges_prompt.tsx @@ -16,6 +16,7 @@ import { UserManagementLink } from './user_management_link'; export const MissingResultsPrivilegesPrompt: React.FunctionComponent = () => ( {missingMlPrivilegesTitle}} body={

    {missingMlResultsPrivilegesDescription}

    } actions={} diff --git a/x-pack/plugins/observability_solution/infra/public/components/logging/log_analysis_setup/missing_setup_privileges_prompt.tsx b/x-pack/plugins/observability_solution/infra/public/components/logging/log_analysis_setup/missing_setup_privileges_prompt.tsx index f959c5035d1a4..4e2a360b55ceb 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/logging/log_analysis_setup/missing_setup_privileges_prompt.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/logging/log_analysis_setup/missing_setup_privileges_prompt.tsx @@ -16,6 +16,7 @@ import { UserManagementLink } from './user_management_link'; export const MissingSetupPrivilegesPrompt: React.FunctionComponent = () => ( {missingMlPrivilegesTitle}} body={

    {missingMlSetupPrivilegesDescription}

    } actions={} diff --git a/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_categories/page.tsx b/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_categories/page.tsx index f5b1e89c69e0b..650a5b119d755 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_categories/page.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_categories/page.tsx @@ -7,8 +7,11 @@ import { EuiErrorBoundary } from '@elastic/eui'; import React from 'react'; +import { MissingResultsPrivilegesPrompt } from '../../../components/logging/log_analysis_setup'; +import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis'; +import { SubscriptionSplashPage } from '../../../components/subscription_splash_content'; import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs'; -import { LogEntryCategoriesPageContent } from './page_content'; +import { CategoriesPageTemplate, LogEntryCategoriesPageContent } from './page_content'; import { LogEntryCategoriesPageProviders } from './page_providers'; import { logCategoriesTitle } from '../../../translations'; import { LogMlJobIdFormatsShimProvider } from '../shared/use_log_ml_job_id_formats_shim'; @@ -20,6 +23,28 @@ export const LogEntryCategoriesPage = () => { }, ]); + const { hasLogAnalysisReadCapabilities, hasLogAnalysisCapabilites } = + useLogAnalysisCapabilitiesContext(); + + if (!hasLogAnalysisCapabilites) { + return ( + + ); + } + + if (!hasLogAnalysisReadCapabilities) { + return ( + + + + ); + } + return ( diff --git a/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_categories/page_content.tsx b/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_categories/page_content.tsx index c58ffc5f36e84..8059cdcb093e2 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_categories/page_content.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_categories/page_content.tsx @@ -13,14 +13,12 @@ import { isJobStatusWithResults, logEntryCategoriesJobType } from '../../../../c import { LoadingPage } from '../../../components/loading_page'; import { LogAnalysisSetupStatusUnknownPrompt, - MissingResultsPrivilegesPrompt, MissingSetupPrivilegesPrompt, } from '../../../components/logging/log_analysis_setup'; import { LogAnalysisSetupFlyout, useLogAnalysisSetupFlyoutStateContext, } from '../../../components/logging/log_analysis_setup/setup_flyout'; -import { SubscriptionSplashPage } from '../../../components/subscription_splash_content'; import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis'; import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories'; import { LogsPageTemplate } from '../shared/page_template'; @@ -33,11 +31,8 @@ const logCategoriesTitle = i18n.translate('xpack.infra.logs.logCategoriesTitle', }); export const LogEntryCategoriesPageContent = () => { - const { - hasLogAnalysisCapabilites, - hasLogAnalysisReadCapabilities, - hasLogAnalysisSetupCapabilities, - } = useLogAnalysisCapabilitiesContext(); + const { hasLogAnalysisReadCapabilities, hasLogAnalysisSetupCapabilities } = + useLogAnalysisCapabilitiesContext(); const { fetchJobStatus, setupStatus, jobStatus } = useLogEntryCategoriesModuleContext(); @@ -55,22 +50,7 @@ export const LogEntryCategoriesPageContent = () => { const { idFormats } = useLogMlJobIdFormatsShimContext(); - if (!hasLogAnalysisCapabilites) { - return ( - - ); - } else if (!hasLogAnalysisReadCapabilities) { - return ( - - - - ); - } else if (setupStatus.type === 'initializing') { + if (setupStatus.type === 'initializing') { return ( { const allowedSetupModules = ['logs_ui_categories' as const]; -const CategoriesPageTemplate: React.FC = ({ +export const CategoriesPageTemplate: React.FC = ({ children, ...rest }) => { diff --git a/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_rate/page.tsx b/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_rate/page.tsx index 97841745ae13a..ed46ea9dc2680 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_rate/page.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_rate/page.tsx @@ -7,7 +7,10 @@ import { EuiErrorBoundary } from '@elastic/eui'; import React from 'react'; -import { LogEntryRatePageContent } from './page_content'; +import { SubscriptionSplashPage } from '../../../components/subscription_splash_content'; +import { MissingResultsPrivilegesPrompt } from '../../../components/logging/log_analysis_setup'; +import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis'; +import { AnomaliesPageTemplate, LogEntryRatePageContent } from './page_content'; import { LogEntryRatePageProviders } from './page_providers'; import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs'; import { logsAnomaliesTitle } from '../../../translations'; @@ -19,6 +22,29 @@ export const LogEntryRatePage = () => { text: logsAnomaliesTitle, }, ]); + + const { hasLogAnalysisReadCapabilities, hasLogAnalysisCapabilites } = + useLogAnalysisCapabilitiesContext(); + + if (!hasLogAnalysisCapabilites) { + return ( + + ); + } + + if (!hasLogAnalysisReadCapabilities) { + return ( + + + + ); + } + return ( diff --git a/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_rate/page_content.tsx b/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_rate/page_content.tsx index 3ac8d7d9137d1..350094b5df6a3 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_rate/page_content.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_rate/page_content.tsx @@ -18,14 +18,12 @@ import { import { LoadingPage } from '../../../components/loading_page'; import { LogAnalysisSetupStatusUnknownPrompt, - MissingResultsPrivilegesPrompt, MissingSetupPrivilegesPrompt, } from '../../../components/logging/log_analysis_setup'; import { LogAnalysisSetupFlyout, useLogAnalysisSetupFlyoutStateContext, } from '../../../components/logging/log_analysis_setup/setup_flyout'; -import { SubscriptionSplashPage } from '../../../components/subscription_splash_content'; import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis'; import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories'; import { useLogEntryRateModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_rate'; @@ -41,11 +39,8 @@ const logsAnomaliesTitle = i18n.translate('xpack.infra.logs.anomaliesPageTitle', }); export const LogEntryRatePageContent = memo(() => { - const { - hasLogAnalysisCapabilites, - hasLogAnalysisReadCapabilities, - hasLogAnalysisSetupCapabilities, - } = useLogAnalysisCapabilitiesContext(); + const { hasLogAnalysisReadCapabilities, hasLogAnalysisSetupCapabilities } = + useLogAnalysisCapabilitiesContext(); const { fetchJobStatus: fetchLogEntryCategoriesJobStatus, @@ -96,22 +91,7 @@ export const LogEntryRatePageContent = memo(() => { const { idFormats } = useLogMlJobIdFormatsShimContext(); - if (!hasLogAnalysisCapabilites) { - return ( - - ); - } else if (!hasLogAnalysisReadCapabilities) { - return ( - - - - ); - } else if ( + if ( logEntryCategoriesSetupStatus.type === 'initializing' || logEntryRateSetupStatus.type === 'initializing' ) { @@ -159,7 +139,7 @@ export const LogEntryRatePageContent = memo(() => { } }); -const AnomaliesPageTemplate: React.FC = ({ +export const AnomaliesPageTemplate: React.FC = ({ children, ...rest }) => { diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx index 27344ccd1f108..68a5db6d4d484 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx @@ -5,21 +5,37 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { LogStream } from '@kbn/logs-shared-plugin/public'; -import { i18n } from '@kbn/i18n'; +import React, { useMemo } from 'react'; import { InfraLoadingPanel } from '../../../../../../components/loading'; +import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; +import { useLogViewReference } from '../../../../../../hooks/use_log_view_reference'; +import { buildCombinedAssetFilter } from '../../../../../../utils/filters/build'; import { useHostsViewContext } from '../../../hooks/use_hosts_view'; -import { useUnifiedSearchContext } from '../../../hooks/use_unified_search'; import { useLogsSearchUrlState } from '../../../hooks/use_logs_search_url_state'; +import { useUnifiedSearchContext } from '../../../hooks/use_unified_search'; import { LogsLinkToStream } from './logs_link_to_stream'; import { LogsSearchBar } from './logs_search_bar'; -import { buildCombinedAssetFilter } from '../../../../../../utils/filters/build'; -import { useLogViewReference } from '../../../../../../hooks/use_log_view_reference'; export const LogsTabContent = () => { + const { + services: { + logsShared: { LogsOverview }, + }, + } = useKibanaContextForPlugin(); + const isLogsOverviewEnabled = LogsOverview.useIsEnabled(); + if (isLogsOverviewEnabled) { + return ; + } else { + return ; + } +}; + +export const LogsTabLogStreamContent = () => { const [filterQuery] = useLogsSearchUrlState(); const { getDateRangeAsTimestamp } = useUnifiedSearchContext(); const { from, to } = useMemo(() => getDateRangeAsTimestamp(), [getDateRangeAsTimestamp]); @@ -53,22 +69,7 @@ export const LogsTabContent = () => { }, [filterQuery.query, hostNodes]); if (loading || logViewLoading || !logView) { - return ( - - - - } - /> - - - ); + return ; } return ( @@ -112,3 +113,53 @@ const createHostsFilterQueryParam = (hostNodes: string[]): string => { return hostsQueryParam; }; + +const LogsTabLogsOverviewContent = () => { + const { + services: { + logsShared: { LogsOverview }, + }, + } = useKibanaContextForPlugin(); + + const { parsedDateRange } = useUnifiedSearchContext(); + const timeRange = useMemo( + () => ({ start: parsedDateRange.from, end: parsedDateRange.to }), + [parsedDateRange.from, parsedDateRange.to] + ); + + const { hostNodes, loading, error } = useHostsViewContext(); + const logFilters = useMemo( + () => [ + buildCombinedAssetFilter({ + field: 'host.name', + values: hostNodes.map((p) => p.name), + }).query as QueryDslQueryContainer, + ], + [hostNodes] + ); + + if (loading) { + return ; + } else if (error != null) { + return ; + } else { + return ; + } +}; + +const LogsTabLoadingContent = () => ( + + + + } + /> + + +); diff --git a/x-pack/plugins/observability_solution/inventory/.storybook/get_mock_inventory_context.tsx b/x-pack/plugins/observability_solution/inventory/.storybook/get_mock_inventory_context.tsx index 51aaeebc655f2..d90ce08aab1c6 100644 --- a/x-pack/plugins/observability_solution/inventory/.storybook/get_mock_inventory_context.tsx +++ b/x-pack/plugins/observability_solution/inventory/.storybook/get_mock_inventory_context.tsx @@ -13,6 +13,7 @@ import type { InferencePublicStart } from '@kbn/inference-plugin/public'; import type { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; +import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { InventoryKibanaContext } from '../public/hooks/use_kibana'; import type { ITelemetryClient } from '../public/services/telemetry/types'; @@ -33,5 +34,6 @@ export function getMockInventoryContext(): InventoryKibanaContext { fetch: jest.fn(), stream: jest.fn(), }, + spaces: {} as unknown as SpacesPluginStart, }; } diff --git a/x-pack/plugins/observability_solution/inventory/kibana.jsonc b/x-pack/plugins/observability_solution/inventory/kibana.jsonc index f60cf36183b24..28556c7bcc583 100644 --- a/x-pack/plugins/observability_solution/inventory/kibana.jsonc +++ b/x-pack/plugins/observability_solution/inventory/kibana.jsonc @@ -19,7 +19,7 @@ "share" ], "requiredBundles": ["kibanaReact"], - "optionalPlugins": [], + "optionalPlugins": ["spaces"], "extraPublicDirs": [] } } diff --git a/x-pack/plugins/observability_solution/inventory/public/plugin.ts b/x-pack/plugins/observability_solution/inventory/public/plugin.ts index c02a57b45f691..30e3a1eed3681 100644 --- a/x-pack/plugins/observability_solution/inventory/public/plugin.ts +++ b/x-pack/plugins/observability_solution/inventory/public/plugin.ts @@ -17,7 +17,7 @@ import { import { INVENTORY_APP_ID } from '@kbn/deeplinks-observability/constants'; import { i18n } from '@kbn/i18n'; import type { Logger } from '@kbn/logging'; -import { from, map } from 'rxjs'; +import { from, map, mergeMap, of } from 'rxjs'; import { createCallInventoryAPI } from './api'; import { TelemetryService } from './services/telemetry/telemetry_service'; import { InventoryServices } from './services/types'; @@ -54,34 +54,53 @@ export class InventoryPlugin 'observability:entityCentricExperience', true ); + const getStartServices = coreSetup.getStartServices(); - if (isEntityCentricExperienceSettingEnabled) { - pluginsSetup.observabilityShared.navigation.registerSections( - from(coreSetup.getStartServices()).pipe( - map(([coreStart, pluginsStart]) => { - return [ - { - label: '', - sortKey: 300, - entries: [ - { - label: i18n.translate('xpack.inventory.inventoryLinkTitle', { - defaultMessage: 'Inventory', - }), - app: INVENTORY_APP_ID, - path: '/', - matchPath(currentPath: string) { - return ['/', ''].some((testPath) => currentPath.startsWith(testPath)); - }, - isTechnicalPreview: true, + const hideInventory$ = from(getStartServices).pipe( + mergeMap(([coreStart, pluginsStart]) => { + if (pluginsStart.spaces) { + return from(pluginsStart.spaces.getActiveSpace()).pipe( + map( + (space) => + space.disabledFeatures.includes(INVENTORY_APP_ID) || + !coreStart.application.capabilities.inventory.show + ) + ); + } + + return of(!coreStart.application.capabilities.inventory.show); + }) + ); + + const sections$ = hideInventory$.pipe( + map((hideInventory) => { + if (isEntityCentricExperienceSettingEnabled && !hideInventory) { + return [ + { + label: '', + sortKey: 300, + entries: [ + { + label: i18n.translate('xpack.inventory.inventoryLinkTitle', { + defaultMessage: 'Inventory', + }), + app: INVENTORY_APP_ID, + path: '/', + matchPath(currentPath: string) { + return ['/', ''].some((testPath) => currentPath.startsWith(testPath)); }, - ], - }, - ]; - }) - ) - ); - } + isTechnicalPreview: true, + }, + ], + }, + ]; + } + return []; + }) + ); + + pluginsSetup.observabilityShared.navigation.registerSections(sections$); + this.telemetry.setup({ analytics: coreSetup.analytics }); const telemetry = this.telemetry.start(); @@ -102,7 +121,7 @@ export class InventoryPlugin // Load application bundle and Get start services const [{ renderApp }, [coreStart, pluginsStart]] = await Promise.all([ import('./application'), - coreSetup.getStartServices(), + getStartServices, ]); const services: InventoryServices = { diff --git a/x-pack/plugins/observability_solution/inventory/public/types.ts b/x-pack/plugins/observability_solution/inventory/public/types.ts index 2393b1b55e2b6..48fe7e7eed1c7 100644 --- a/x-pack/plugins/observability_solution/inventory/public/types.ts +++ b/x-pack/plugins/observability_solution/inventory/public/types.ts @@ -17,6 +17,7 @@ import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/publi import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; /* eslint-disable @typescript-eslint/no-empty-interface*/ @@ -38,6 +39,7 @@ export interface InventoryStartDependencies { data: DataPublicPluginStart; entityManager: EntityManagerPublicPluginStart; share: SharePluginStart; + spaces?: SpacesPluginStart; } export interface InventoryPublicSetup {} diff --git a/x-pack/plugins/observability_solution/inventory/tsconfig.json b/x-pack/plugins/observability_solution/inventory/tsconfig.json index 54fcfe7e3a11f..20b5e2e37232a 100644 --- a/x-pack/plugins/observability_solution/inventory/tsconfig.json +++ b/x-pack/plugins/observability_solution/inventory/tsconfig.json @@ -45,6 +45,7 @@ "@kbn/config-schema", "@kbn/elastic-agent-utils", "@kbn/custom-icons", - "@kbn/ui-theme" + "@kbn/ui-theme", + "@kbn/spaces-plugin" ] } diff --git a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc index ea93fd326dac7..10c8fe32cfe9c 100644 --- a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc +++ b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc @@ -9,13 +9,14 @@ "browser": true, "configPath": ["xpack", "logs_shared"], "requiredPlugins": [ + "charts", "data", "dataViews", "discoverShared", - "usageCollection", + "logsDataAccess", "observabilityShared", "share", - "logsDataAccess" + "usageCollection", ], "optionalPlugins": [ "observabilityAIAssistant", diff --git a/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/index.tsx b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/index.tsx new file mode 100644 index 0000000000000..627cdc8447eea --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/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 * from './logs_overview'; diff --git a/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/logs_overview.mock.tsx b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/logs_overview.mock.tsx new file mode 100644 index 0000000000000..435766bff793d --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/logs_overview.mock.tsx @@ -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 React from 'react'; +import type { + LogsOverviewProps, + SelfContainedLogsOverviewComponent, + SelfContainedLogsOverviewHelpers, +} from './logs_overview'; + +export const createLogsOverviewMock = () => { + const LogsOverviewMock = jest.fn(LogsOverviewMockImpl) as unknown as ILogsOverviewMock; + + LogsOverviewMock.useIsEnabled = jest.fn(() => true); + + LogsOverviewMock.ErrorContent = jest.fn(() =>
    ); + + LogsOverviewMock.LoadingContent = jest.fn(() =>
    ); + + return LogsOverviewMock; +}; + +const LogsOverviewMockImpl = (_props: LogsOverviewProps) => { + return
    ; +}; + +type ILogsOverviewMock = jest.Mocked & + jest.Mocked; diff --git a/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/logs_overview.tsx b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/logs_overview.tsx new file mode 100644 index 0000000000000..7b60aee5be57c --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/logs_overview.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { OBSERVABILITY_LOGS_SHARED_NEW_LOGS_OVERVIEW_ID } from '@kbn/management-settings-ids'; +import type { + LogsOverviewProps as FullLogsOverviewProps, + LogsOverviewDependencies, + LogsOverviewErrorContentProps, +} from '@kbn/observability-logs-overview'; +import { dynamic } from '@kbn/shared-ux-utility'; +import React from 'react'; +import useObservable from 'react-use/lib/useObservable'; + +const LazyLogsOverview = dynamic(() => + import('@kbn/observability-logs-overview').then((mod) => ({ default: mod.LogsOverview })) +); + +const LazyLogsOverviewErrorContent = dynamic(() => + import('@kbn/observability-logs-overview').then((mod) => ({ + default: mod.LogsOverviewErrorContent, + })) +); + +const LazyLogsOverviewLoadingContent = dynamic(() => + import('@kbn/observability-logs-overview').then((mod) => ({ + default: mod.LogsOverviewLoadingContent, + })) +); + +export type LogsOverviewProps = Omit; + +export interface SelfContainedLogsOverviewHelpers { + useIsEnabled: () => boolean; + ErrorContent: React.ComponentType; + LoadingContent: React.ComponentType; +} + +export type SelfContainedLogsOverviewComponent = React.ComponentType; + +export type SelfContainedLogsOverview = SelfContainedLogsOverviewComponent & + SelfContainedLogsOverviewHelpers; + +export const createLogsOverview = ( + dependencies: LogsOverviewDependencies +): SelfContainedLogsOverview => { + const SelfContainedLogsOverview = (props: LogsOverviewProps) => { + return ; + }; + + const isEnabled$ = dependencies.uiSettings.client.get$( + OBSERVABILITY_LOGS_SHARED_NEW_LOGS_OVERVIEW_ID, + defaultIsEnabled + ); + + SelfContainedLogsOverview.useIsEnabled = (): boolean => { + return useObservable(isEnabled$, defaultIsEnabled); + }; + + SelfContainedLogsOverview.ErrorContent = LazyLogsOverviewErrorContent; + + SelfContainedLogsOverview.LoadingContent = LazyLogsOverviewLoadingContent; + + return SelfContainedLogsOverview; +}; + +const defaultIsEnabled = false; diff --git a/x-pack/plugins/observability_solution/logs_shared/public/index.ts b/x-pack/plugins/observability_solution/logs_shared/public/index.ts index a602b25786116..3d601c9936f2d 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/index.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/index.ts @@ -50,6 +50,7 @@ export type { UpdatedDateRange, VisibleInterval, } from './components/logging/log_text_stream/scrollable_log_text_stream_view'; +export type { LogsOverviewProps } from './components/logs_overview'; export const WithSummary = dynamic(() => import('./containers/logs/log_summary/with_summary')); export const LogEntryFlyout = dynamic( diff --git a/x-pack/plugins/observability_solution/logs_shared/public/mocks.tsx b/x-pack/plugins/observability_solution/logs_shared/public/mocks.tsx index a9b0ebd6a6aa3..ffb867abbcc17 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/mocks.tsx +++ b/x-pack/plugins/observability_solution/logs_shared/public/mocks.tsx @@ -6,12 +6,14 @@ */ import { createLogAIAssistantMock } from './components/log_ai_assistant/log_ai_assistant.mock'; +import { createLogsOverviewMock } from './components/logs_overview/logs_overview.mock'; import { createLogViewsServiceStartMock } from './services/log_views/log_views_service.mock'; import { LogsSharedClientStartExports } from './types'; export const createLogsSharedPluginStartMock = (): jest.Mocked => ({ logViews: createLogViewsServiceStartMock(), LogAIAssistant: createLogAIAssistantMock(), + LogsOverview: createLogsOverviewMock(), }); export const _ensureTypeCompatibility = (): LogsSharedClientStartExports => diff --git a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts b/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts index d6f4ac81fe266..fc17e9b17cc82 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts @@ -12,6 +12,7 @@ import { TraceLogsLocatorDefinition, } from '../common/locators'; import { createLogAIAssistant, createLogsAIAssistantRenderer } from './components/log_ai_assistant'; +import { createLogsOverview } from './components/logs_overview'; import { LogViewsService } from './services/log_views'; import { LogsSharedClientCoreSetup, @@ -51,8 +52,16 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass { } public start(core: CoreStart, plugins: LogsSharedClientStartDeps) { - const { http } = core; - const { data, dataViews, discoverShared, observabilityAIAssistant, logsDataAccess } = plugins; + const { http, settings } = core; + const { + charts, + data, + dataViews, + discoverShared, + logsDataAccess, + observabilityAIAssistant, + share, + } = plugins; const logViews = this.logViews.start({ http, @@ -61,9 +70,18 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass { search: data.search, }); + const LogsOverview = createLogsOverview({ + charts, + logsDataAccess, + search: data.search.search, + uiSettings: settings, + share, + }); + if (!observabilityAIAssistant) { return { logViews, + LogsOverview, }; } @@ -77,6 +95,7 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass { return { logViews, LogAIAssistant, + LogsOverview, }; } diff --git a/x-pack/plugins/observability_solution/logs_shared/public/types.ts b/x-pack/plugins/observability_solution/logs_shared/public/types.ts index 58b180ee8b6ef..4237c28c621b8 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/types.ts @@ -5,19 +5,19 @@ * 2.0. */ +import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import type { CoreSetup, CoreStart, Plugin as PluginClass } from '@kbn/core/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public'; -import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; +import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public'; import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; - -import { LogsSharedLocators } from '../common/locators'; +import type { LogsSharedLocators } from '../common/locators'; import type { LogAIAssistantProps } from './components/log_ai_assistant/log_ai_assistant'; -// import type { OsqueryPluginStart } from '../../osquery/public'; -import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views'; +import type { SelfContainedLogsOverview } from './components/logs_overview'; +import type { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views'; // Our own setup and start contract values export interface LogsSharedClientSetupExports { @@ -28,6 +28,7 @@ export interface LogsSharedClientSetupExports { export interface LogsSharedClientStartExports { logViews: LogViewsServiceStart; LogAIAssistant?: (props: Omit) => JSX.Element; + LogsOverview: SelfContainedLogsOverview; } export interface LogsSharedClientSetupDeps { @@ -35,6 +36,7 @@ export interface LogsSharedClientSetupDeps { } export interface LogsSharedClientStartDeps { + charts: ChartsPluginStart; data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; discoverShared: DiscoverSharedPublicStart; diff --git a/x-pack/plugins/observability_solution/logs_shared/server/feature_flags.ts b/x-pack/plugins/observability_solution/logs_shared/server/feature_flags.ts new file mode 100644 index 0000000000000..0298416bd3f26 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_shared/server/feature_flags.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 { schema } from '@kbn/config-schema'; +import { UiSettingsParams } from '@kbn/core-ui-settings-common'; +import { i18n } from '@kbn/i18n'; +import { OBSERVABILITY_LOGS_SHARED_NEW_LOGS_OVERVIEW_ID } from '@kbn/management-settings-ids'; + +const technicalPreviewLabel = i18n.translate('xpack.logsShared.technicalPreviewSettingLabel', { + defaultMessage: 'Technical Preview', +}); + +export const featureFlagUiSettings: Record = { + [OBSERVABILITY_LOGS_SHARED_NEW_LOGS_OVERVIEW_ID]: { + category: ['observability'], + name: i18n.translate('xpack.logsShared.newLogsOverviewSettingName', { + defaultMessage: 'New logs overview', + }), + value: false, + description: i18n.translate('xpack.logsShared.newLogsOverviewSettingDescription', { + defaultMessage: '{technicalPreviewLabel} Enable the new logs overview experience.', + + values: { technicalPreviewLabel: `[${technicalPreviewLabel}]` }, + }), + type: 'boolean', + schema: schema.boolean(), + requiresPageReload: true, + }, +}; diff --git a/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts b/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts index 7c97e175ed64f..d1f6399104fc2 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts @@ -5,8 +5,19 @@ * 2.0. */ -import { PluginInitializerContext, CoreStart, Plugin, Logger } from '@kbn/core/server'; - +import { CoreStart, Logger, Plugin, PluginInitializerContext } from '@kbn/core/server'; +import { defaultLogViewId } from '../common/log_views'; +import { LogsSharedConfig } from '../common/plugin_config'; +import { registerDeprecations } from './deprecations'; +import { featureFlagUiSettings } from './feature_flags'; +import { KibanaFramework } from './lib/adapters/framework/kibana_framework_adapter'; +import { LogsSharedKibanaLogEntriesAdapter } from './lib/adapters/log_entries/kibana_log_entries_adapter'; +import { LogsSharedLogEntriesDomain } from './lib/domains/log_entries_domain'; +import { LogsSharedBackendLibs, LogsSharedDomainLibs } from './lib/logs_shared_types'; +import { initLogsSharedServer } from './logs_shared_server'; +import { logViewSavedObjectType } from './saved_objects'; +import { LogEntriesService } from './services/log_entries'; +import { LogViewsService } from './services/log_views'; import { LogsSharedPluginCoreSetup, LogsSharedPluginSetup, @@ -15,17 +26,6 @@ import { LogsSharedServerPluginStartDeps, UsageCollector, } from './types'; -import { logViewSavedObjectType } from './saved_objects'; -import { initLogsSharedServer } from './logs_shared_server'; -import { LogViewsService } from './services/log_views'; -import { KibanaFramework } from './lib/adapters/framework/kibana_framework_adapter'; -import { LogsSharedBackendLibs, LogsSharedDomainLibs } from './lib/logs_shared_types'; -import { LogsSharedLogEntriesDomain } from './lib/domains/log_entries_domain'; -import { LogsSharedKibanaLogEntriesAdapter } from './lib/adapters/log_entries/kibana_log_entries_adapter'; -import { LogEntriesService } from './services/log_entries'; -import { LogsSharedConfig } from '../common/plugin_config'; -import { registerDeprecations } from './deprecations'; -import { defaultLogViewId } from '../common/log_views'; export class LogsSharedPlugin implements @@ -88,6 +88,8 @@ export class LogsSharedPlugin registerDeprecations({ core }); + core.uiSettings.register(featureFlagUiSettings); + return { ...domainLibs, logViews, diff --git a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json index 38cbba7c252c0..788f55c9b6fc5 100644 --- a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json +++ b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json @@ -44,5 +44,9 @@ "@kbn/logs-data-access-plugin", "@kbn/core-deprecations-common", "@kbn/core-deprecations-server", + "@kbn/management-settings-ids", + "@kbn/observability-logs-overview", + "@kbn/charts-plugin", + "@kbn/core-ui-settings-common", ] } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/assets/elastic_ai_assistant.png b/x-pack/plugins/observability_solution/observability_ai_assistant/public/assets/elastic_ai_assistant.png new file mode 100644 index 0000000000000..af10645579683 Binary files /dev/null and b/x-pack/plugins/observability_solution/observability_ai_assistant/public/assets/elastic_ai_assistant.png differ diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/index.ts index ab2dea089dcf1..76e643c6ae0d5 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/index.ts @@ -19,6 +19,7 @@ import type { RenderFunction, DiscoveredDataset, } from './types'; +import elasticAiAssistantImg from './assets/elastic_ai_assistant.png'; export type { ObservabilityAIAssistantPublicSetup, @@ -101,6 +102,8 @@ export { aiAssistantPreferredAIAssistantType, } from '../common/ui_settings/settings_keys'; +export const elasticAiAssistantImage = elasticAiAssistantImg; + export const plugin: PluginInitializer< ObservabilityAIAssistantPublicSetup, ObservabilityAIAssistantPublicStart, diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/application.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/application.tsx index c554fc81d5de7..ce043ef395ee4 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/application.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/application.tsx @@ -9,10 +9,10 @@ import { RouteRenderer, RouterProvider } from '@kbn/typed-react-router-config'; import type { History } from 'history'; import React from 'react'; import type { Observable } from 'rxjs'; -import { observabilityAIAssistantRouter } from './routes/config'; -import type { ObservabilityAIAssistantAppService } from './service/create_app_service'; +import type { AIAssistantAppService } from '@kbn/ai-assistant'; import type { ObservabilityAIAssistantAppPluginStartDependencies } from './types'; import { SharedProviders } from './utils/shared_providers'; +import { observabilityAIAssistantRouter } from './routes/config'; // This is the Conversation application. @@ -26,7 +26,7 @@ export function Application({ coreStart: CoreStart; history: History; pluginsStart: ObservabilityAIAssistantAppPluginStartDependencies; - service: ObservabilityAIAssistantAppService; + service: AIAssistantAppService; theme$: Observable; }) { return ( @@ -36,7 +36,7 @@ export function Application({ service={service} theme$={theme$} > - + diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/index.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/index.tsx index 66a66ecc07dc0..2c2af65accb59 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/index.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/index.tsx @@ -12,17 +12,15 @@ import { v4 } from 'uuid'; import useObservable from 'react-use/lib/useObservable'; import { i18n } from '@kbn/i18n'; import { CoreStart } from '@kbn/core-lifecycle-browser'; -import { useObservabilityAIAssistantAppService } from '../../hooks/use_observability_ai_assistant_app_service'; -import { ChatFlyout } from '../chat/chat_flyout'; +import { AIAssistantAppService, useAIAssistantAppService, ChatFlyout } from '@kbn/ai-assistant'; import { useKibana } from '../../hooks/use_kibana'; import { useTheme } from '../../hooks/use_theme'; import { useNavControlScreenContext } from '../../hooks/use_nav_control_screen_context'; import { SharedProviders } from '../../utils/shared_providers'; -import { ObservabilityAIAssistantAppService } from '../../service/create_app_service'; import { ObservabilityAIAssistantAppPluginStartDependencies } from '../../types'; interface NavControlWithProviderDeps { - appService: ObservabilityAIAssistantAppService; + appService: AIAssistantAppService; coreStart: CoreStart; pluginsStart: ObservabilityAIAssistantAppPluginStartDependencies; } @@ -45,10 +43,12 @@ export const NavControlWithProvider = ({ }; export function NavControl() { - const service = useObservabilityAIAssistantAppService(); + const service = useAIAssistantAppService(); const { services: { + application, + http, notifications, plugins: { start: { @@ -162,6 +162,13 @@ export function NavControl() { onClose={() => { setIsOpen(false); }} + navigateToConversation={(conversationId: string) => { + application.navigateToUrl( + http.basePath.prepend( + `/app/observabilityAIAssistant/conversations/${conversationId || ''}` + ) + ); + }} /> ) : undefined} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/lazy_nav_control.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/lazy_nav_control.tsx index bed86909af417..adef91ceea53e 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/lazy_nav_control.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/lazy_nav_control.tsx @@ -8,8 +8,8 @@ import { dynamic } from '@kbn/shared-ux-utility'; import React from 'react'; import { CoreStart } from '@kbn/core-lifecycle-browser'; +import { AIAssistantAppService } from '@kbn/ai-assistant'; import { useIsNavControlVisible } from '../../hooks/is_nav_control_visible'; -import { ObservabilityAIAssistantAppService } from '../../service/create_app_service'; import { ObservabilityAIAssistantAppPluginStartDependencies } from '../../types'; const LazyNavControlWithProvider = dynamic(() => @@ -17,7 +17,7 @@ const LazyNavControlWithProvider = dynamic(() => ); interface NavControlInitiatorProps { - appService: ObservabilityAIAssistantAppService; + appService: AIAssistantAppService; coreStart: CoreStart; pluginsStart: ObservabilityAIAssistantAppPluginStartDependencies; } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/context/observability_ai_assistant_app_service_provider.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/context/observability_ai_assistant_app_service_provider.tsx deleted file mode 100644 index 9de7f023b4d10..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/context/observability_ai_assistant_app_service_provider.tsx +++ /dev/null @@ -1,16 +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 { createContext } from 'react'; -import type { ObservabilityAIAssistantAppService } from '../service/create_app_service'; - -export const ObservabilityAIAssistantAppServiceContext = createContext< - ObservabilityAIAssistantAppService | undefined ->(undefined); - -export const ObservabilityAIAssistantAppServiceProvider = - ObservabilityAIAssistantAppServiceContext.Provider; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_kibana.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_kibana.ts index f836c3dac6159..deaabffeeb50d 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_kibana.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_kibana.ts @@ -7,10 +7,13 @@ import React from 'react'; import { Subject } from 'rxjs'; -import { useChat } from './use_chat'; const ObservabilityAIAssistantMultipaneFlyoutContext = React.createContext(undefined); +function useChat() { + return { next: () => {}, messages: [], setMessages: () => {}, state: undefined, stop: () => {} }; +} + export function useKibana() { return { services: { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_nav_control_screen_context.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_nav_control_screen_context.ts index 10195bf38651e..d068f592c4310 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_nav_control_screen_context.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_nav_control_screen_context.ts @@ -8,11 +8,11 @@ import { useEffect, useState } from 'react'; import datemath from '@elastic/datemath'; import moment from 'moment'; +import { useAIAssistantAppService } from '@kbn/ai-assistant'; import { useKibana } from './use_kibana'; -import { useObservabilityAIAssistantAppService } from './use_observability_ai_assistant_app_service'; export function useNavControlScreenContext() { - const service = useObservabilityAIAssistantAppService(); + const service = useAIAssistantAppService(); const { services: { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_observability_ai_assistant_app_service.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_observability_ai_assistant_app_service.ts deleted file mode 100644 index 9c86f29565f48..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_observability_ai_assistant_app_service.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { useContext } from 'react'; -import { ObservabilityAIAssistantAppServiceContext } from '../context/observability_ai_assistant_app_service_provider'; - -export function useObservabilityAIAssistantAppService() { - const services = useContext(ObservabilityAIAssistantAppServiceContext); - - if (!services) { - throw new Error( - 'ObservabilityAIAssistantContext not set. Did you wrap your component in ``?' - ); - } - - return services; -} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/i18n.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/i18n.ts deleted file mode 100644 index dcc28d7ff531a..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/i18n.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const ASSISTANT_SETUP_TITLE = i18n.translate( - 'xpack.observabilityAiAssistant.assistantSetup.title', - { - defaultMessage: 'Welcome to Elastic AI Assistant', - } -); - -export const EMPTY_CONVERSATION_TITLE = i18n.translate( - 'xpack.observabilityAiAssistant.emptyConversationTitle', - { defaultMessage: 'New conversation' } -); - -export const UPGRADE_LICENSE_TITLE = i18n.translate( - 'xpack.observabilityAiAssistant.incorrectLicense.title', - { - defaultMessage: 'Upgrade your license', - } -); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/plugin.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/plugin.tsx index 9817cc65362d6..1904eebffb2a8 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/plugin.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/plugin.tsx @@ -17,13 +17,13 @@ import { import type { Logger } from '@kbn/logging'; import { i18n } from '@kbn/i18n'; import { AI_ASSISTANT_APP_ID } from '@kbn/deeplinks-observability'; +import { createAppService, AIAssistantAppService } from '@kbn/ai-assistant'; import type { ObservabilityAIAssistantAppPluginSetupDependencies, ObservabilityAIAssistantAppPluginStartDependencies, ObservabilityAIAssistantAppPublicSetup, ObservabilityAIAssistantAppPublicStart, } from './types'; -import { createAppService, ObservabilityAIAssistantAppService } from './service/create_app_service'; import { getObsAIAssistantConnectorType } from './rule_connector'; import { NavControlInitiator } from './components/nav_control/lazy_nav_control'; @@ -40,7 +40,7 @@ export class ObservabilityAIAssistantAppPlugin > { logger: Logger; - appService: ObservabilityAIAssistantAppService | undefined; + appService: AIAssistantAppService | undefined; constructor(context: PluginInitializerContext) { this.logger = context.logger.get(); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/config.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/config.tsx index ed0ac18302cc5..545c69a990ace 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/config.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/config.tsx @@ -9,8 +9,8 @@ import { createRouter, Outlet } from '@kbn/typed-react-router-config'; import * as t from 'io-ts'; import React from 'react'; import { Redirect } from 'react-router-dom'; +import { ConversationViewWithProps } from './conversations/conversation_view_with_props'; import { ObservabilityAIAssistantPageTemplate } from '../components/page_template'; -import { ConversationView } from './conversations/conversation_view'; /** * The array of route definitions to be used when the application @@ -28,7 +28,7 @@ const observabilityAIAssistantRoutes = { ), children: { '/conversations/new': { - element: , + element: , }, '/conversations/{conversationId}': { params: t.intersection([ @@ -43,7 +43,7 @@ const observabilityAIAssistantRoutes = { }), }), ]), - element: , + element: , }, '/conversations': { element: , diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view_with_props.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view_with_props.tsx new file mode 100644 index 0000000000000..c57b8e2c66c71 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view_with_props.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { ConversationView } from '@kbn/ai-assistant'; +import { useObservabilityAIAssistantParams } from '../../hooks/use_observability_ai_assistant_params'; +import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router'; + +export function ConversationViewWithProps() { + const { path } = useObservabilityAIAssistantParams('/conversations/*'); + const conversationId = 'conversationId' in path ? path.conversationId : undefined; + const observabilityAIAssistantRouter = useObservabilityAIAssistantRouter(); + function navigateToConversation(nextConversationId?: string) { + if (nextConversationId) { + observabilityAIAssistantRouter.push('/conversations/{conversationId}', { + path: { + conversationId: nextConversationId, + }, + query: {}, + }); + } else { + observabilityAIAssistantRouter.push('/conversations/new', { path: {}, query: {} }); + } + } + return ( + + observabilityAIAssistantRouter.link(`/conversations/{conversationId}`, { + path: { + conversationId: id, + }, + }) + } + /> + ); +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/shared_providers.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/shared_providers.tsx index eaa441b34a008..49776f4622250 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/shared_providers.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/shared_providers.tsx @@ -11,8 +11,7 @@ import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import React, { useMemo } from 'react'; import type { Observable } from 'rxjs'; -import { ObservabilityAIAssistantAppServiceProvider } from '../context/observability_ai_assistant_app_service_provider'; -import type { ObservabilityAIAssistantAppService } from '../service/create_app_service'; +import { AIAssistantAppService } from '@kbn/ai-assistant'; import type { ObservabilityAIAssistantAppPluginStartDependencies } from '../types'; export function SharedProviders({ @@ -25,7 +24,7 @@ export function SharedProviders({ children: React.ReactElement; coreStart: CoreStart; pluginsStart: ObservabilityAIAssistantAppPluginStartDependencies; - service: ObservabilityAIAssistantAppService; + service: AIAssistantAppService; theme$: Observable; }) { const theme = useMemo(() => { @@ -45,11 +44,7 @@ export function SharedProviders({ }} > - - - {children} - - + {children} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json index 84fe8f0b93911..f5b6d1db53885 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json @@ -20,16 +20,8 @@ "@kbn/typed-react-router-config", "@kbn/i18n", "@kbn/management-settings-ids", - "@kbn/security-plugin", - "@kbn/ui-theme", - "@kbn/actions-plugin", - "@kbn/user-profile-components", - "@kbn/core-http-browser", "@kbn/triggers-actions-ui-plugin", "@kbn/shared-ux-utility", - "@kbn/i18n-react", - "@kbn/code-editor", - "@kbn/monaco", "@kbn/data-views-plugin", "@kbn/lens-embeddable-utils", "@kbn/lens-plugin", @@ -38,7 +30,6 @@ "@kbn/esql-utils", "@kbn/visualization-utils", "@kbn/ai-assistant-management-plugin", - "@kbn/utility-types-jest", "@kbn/kibana-react-plugin", "@kbn/licensing-plugin", "@kbn/logging", @@ -56,6 +47,11 @@ "@kbn/apm-synthtrace-client", "@kbn/alerting-plugin", "@kbn/apm-synthtrace", + "@kbn/esql-datagrid", + "@kbn/alerting-comparators", + "@kbn/core-lifecycle-browser", + "@kbn/inference-plugin", + "@kbn/ai-assistant", "@kbn/apm-utils", "@kbn/config-schema", "@kbn/es-query", @@ -63,17 +59,17 @@ "@kbn/esql-validation-autocomplete", "@kbn/esql-ast", "@kbn/field-types", + "@kbn/security-plugin", + "@kbn/observability-plugin", + "@kbn/actions-plugin", "@kbn/stack-connectors-plugin", "@kbn/features-plugin", "@kbn/serverless", "@kbn/task-manager-plugin", "@kbn/cloud-plugin", - "@kbn/observability-plugin", - "@kbn/esql-datagrid", - "@kbn/alerting-comparators", - "@kbn/core-lifecycle-browser", - "@kbn/inference-plugin", - "@kbn/logs-data-access-plugin" + "@kbn/logs-data-access-plugin", ], - "exclude": ["target/**/*"] + "exclude": [ + "target/**/*" + ] } diff --git a/x-pack/plugins/search_assistant/kibana.jsonc b/x-pack/plugins/search_assistant/kibana.jsonc index 85579b76a1e80..8391ee14e0d88 100644 --- a/x-pack/plugins/search_assistant/kibana.jsonc +++ b/x-pack/plugins/search_assistant/kibana.jsonc @@ -12,13 +12,19 @@ "searchAssistant" ], "requiredPlugins": [ + "actions", + "licensing", "observabilityAIAssistant", - "observabilityAIAssistantApp" + "observabilityAIAssistantApp", + "triggersActionsUi", + "share" ], "optionalPlugins": [ "cloud", "usageCollection", ], - "requiredBundles": [] + "requiredBundles": [ + "kibanaReact" + ] } } diff --git a/x-pack/plugins/search_assistant/public/application.tsx b/x-pack/plugins/search_assistant/public/application.tsx index 071c51f4b6e13..1bbf7063ec373 100644 --- a/x-pack/plugins/search_assistant/public/application.tsx +++ b/x-pack/plugins/search_assistant/public/application.tsx @@ -7,31 +7,28 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import type { CoreStart } from '@kbn/core/public'; +import type { AppMountParameters, CoreStart } from '@kbn/core/public'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { I18nProvider } from '@kbn/i18n-react'; -import { Router } from '@kbn/shared-ux-router'; import type { SearchAssistantPluginStartDependencies } from './types'; -import { SearchAssistantRouter } from './router'; +import { SearchAssistantRouter } from './components/routes/router'; export const renderApp = ( core: CoreStart, services: SearchAssistantPluginStartDependencies, - element: HTMLElement + appMountParameters: AppMountParameters ) => { ReactDOM.render( - - - + , - element + appMountParameters.element ); - return () => ReactDOM.unmountComponentAtNode(element); + return () => ReactDOM.unmountComponentAtNode(appMountParameters.element); }; diff --git a/x-pack/plugins/search_assistant/public/components/page_template.tsx b/x-pack/plugins/search_assistant/public/components/page_template.tsx new file mode 100644 index 0000000000000..e9fb3a45e9e2b --- /dev/null +++ b/x-pack/plugins/search_assistant/public/components/page_template.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 React from 'react'; +import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; + +export function SearchAIAssistantPageTemplate({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/x-pack/plugins/search_assistant/public/components/routes/conversations/conversation_view_with_props.tsx b/x-pack/plugins/search_assistant/public/components/routes/conversations/conversation_view_with_props.tsx new file mode 100644 index 0000000000000..545ff1ceb7370 --- /dev/null +++ b/x-pack/plugins/search_assistant/public/components/routes/conversations/conversation_view_with_props.tsx @@ -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. + */ + +import React from 'react'; +import { ConversationView } from '@kbn/ai-assistant'; +import { useParams } from 'react-router-dom'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; + +export function ConversationViewWithProps() { + const { conversationId } = useParams<{ conversationId?: string }>(); + const { + services: { application, http }, + } = useKibana(); + function navigateToConversation(nextConversationId?: string) { + application?.navigateToUrl( + http?.basePath.prepend(`/app/searchAssistant/conversations/${nextConversationId || ''}`) || '' + ); + } + return ( + + http?.basePath.prepend(`/app/searchAssistant/conversations/${id || ''}`) || '' + } + /> + ); +} diff --git a/x-pack/plugins/search_assistant/public/components/routes/router.tsx b/x-pack/plugins/search_assistant/public/components/routes/router.tsx new file mode 100644 index 0000000000000..154bc2ab46a3e --- /dev/null +++ b/x-pack/plugins/search_assistant/public/components/routes/router.tsx @@ -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 React from 'react'; +import { History } from 'history'; +import { Route, Router, Routes } from '@kbn/shared-ux-router'; +import { Redirect } from 'react-router-dom'; +import { SearchAIAssistantPageTemplate } from '../page_template'; +import { ConversationViewWithProps } from './conversations/conversation_view_with_props'; + +export const SearchAssistantRouter: React.FC<{ history: History }> = ({ history }) => { + return ( + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/search_assistant/public/components/search_assistant.tsx b/x-pack/plugins/search_assistant/public/components/search_assistant.tsx deleted file mode 100644 index 9c227a4e7b73f..0000000000000 --- a/x-pack/plugins/search_assistant/public/components/search_assistant.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiPageTemplate } from '@elastic/eui'; -import React from 'react'; -import { App } from './app'; - -export const SearchAssistantPage: React.FC = () => { - return ( - - - - ); -}; diff --git a/x-pack/plugins/search_assistant/public/index.ts b/x-pack/plugins/search_assistant/public/index.ts index c2b16e857b53e..cb84f8519fd96 100644 --- a/x-pack/plugins/search_assistant/public/index.ts +++ b/x-pack/plugins/search_assistant/public/index.ts @@ -5,9 +5,19 @@ * 2.0. */ -import { SearchAssistantPlugin } from './plugin'; +import { PluginInitializer, PluginInitializerContext } from '@kbn/core/public'; +import { PublicConfigType, SearchAssistantPlugin } from './plugin'; +import { + SearchAssistantPluginSetup, + SearchAssistantPluginStart, + SearchAssistantPluginStartDependencies, +} from './types'; + +export const plugin: PluginInitializer< + SearchAssistantPluginSetup, + SearchAssistantPluginStart, + {}, + SearchAssistantPluginStartDependencies +> = (context: PluginInitializerContext) => new SearchAssistantPlugin(context); -export function plugin() { - return new SearchAssistantPlugin(); -} export type { SearchAssistantPluginSetup, SearchAssistantPluginStart } from './types'; diff --git a/x-pack/plugins/search_assistant/public/plugin.ts b/x-pack/plugins/search_assistant/public/plugin.ts index 8ba22a48df9ff..1c09502c154ad 100644 --- a/x-pack/plugins/search_assistant/public/plugin.ts +++ b/x-pack/plugins/search_assistant/public/plugin.ts @@ -5,19 +5,71 @@ * 2.0. */ -import type { CoreSetup, Plugin } from '@kbn/core/public'; +import { + DEFAULT_APP_CATEGORIES, + type CoreSetup, + type Plugin, + CoreStart, + AppMountParameters, + PluginInitializerContext, +} from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; import type { SearchAssistantPluginSetup, SearchAssistantPluginStart, SearchAssistantPluginStartDependencies, } from './types'; +export interface PublicConfigType { + ui: { + enabled: boolean; + }; +} + export class SearchAssistantPlugin - implements Plugin + implements + Plugin< + SearchAssistantPluginSetup, + SearchAssistantPluginStart, + {}, + SearchAssistantPluginStartDependencies + > { + private readonly config: PublicConfigType; + + constructor(private readonly context: PluginInitializerContext) { + this.config = this.context.config.get(); + } + public setup( core: CoreSetup ): SearchAssistantPluginSetup { + if (!this.config.ui.enabled) { + return {}; + } + + core.application.register({ + id: 'searchAssistant', + title: i18n.translate('xpack.searchAssistant.appTitle', { + defaultMessage: 'Search Assistant', + }), + euiIconType: 'logoEnterpriseSearch', + appRoute: '/app/searchAssistant', + category: DEFAULT_APP_CATEGORIES.search, + visibleIn: [], + deepLinks: [], + mount: async (appMountParameters: AppMountParameters) => { + // Load application bundle and Get start services + const [{ renderApp }, [coreStart, pluginsStart]] = await Promise.all([ + import('./application'), + core.getStartServices() as Promise< + [CoreStart, SearchAssistantPluginStartDependencies, unknown] + >, + ]); + + return renderApp(coreStart, pluginsStart, appMountParameters); + }, + }); return {}; } diff --git a/x-pack/plugins/search_assistant/public/router.tsx b/x-pack/plugins/search_assistant/public/router.tsx deleted file mode 100644 index a25f865b4f74a..0000000000000 --- a/x-pack/plugins/search_assistant/public/router.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Route, Routes } from '@kbn/shared-ux-router'; -import React from 'react'; -import { SearchAssistantPage } from './components/search_assistant'; - -export const SearchAssistantRouter: React.FC = () => { - return ( - - - - - - ); -}; diff --git a/x-pack/plugins/search_assistant/public/types.ts b/x-pack/plugins/search_assistant/public/types.ts index f05592414a9dc..b1a5d6164b1f1 100644 --- a/x-pack/plugins/search_assistant/public/types.ts +++ b/x-pack/plugins/search_assistant/public/types.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { AppMountParameters } from '@kbn/core/public'; import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public'; @@ -16,7 +15,6 @@ export interface SearchAssistantPluginSetup {} export interface SearchAssistantPluginStart {} export interface SearchAssistantPluginStartDependencies { - history: AppMountParameters['history']; observabilityAIAssistant: ObservabilityAIAssistantPublicStart; usageCollection?: UsageCollectionStart; } diff --git a/x-pack/plugins/search_assistant/server/config.ts b/x-pack/plugins/search_assistant/server/config.ts index a09b7ac51b7b7..5ca081ec8a667 100644 --- a/x-pack/plugins/search_assistant/server/config.ts +++ b/x-pack/plugins/search_assistant/server/config.ts @@ -9,11 +9,19 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { PluginConfigDescriptor } from '@kbn/core/server'; const configSchema = schema.object({ - enabled: schema.boolean({ defaultValue: false }), + enabled: schema.boolean({ defaultValue: true }), + ui: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), }); export type SearchAssistantConfig = TypeOf; export const config: PluginConfigDescriptor = { + exposeToBrowser: { + ui: { + enabled: true, + }, + }, schema: configSchema, }; diff --git a/x-pack/plugins/search_assistant/tsconfig.json b/x-pack/plugins/search_assistant/tsconfig.json index 090356cf1f440..d865d2bdbff83 100644 --- a/x-pack/plugins/search_assistant/tsconfig.json +++ b/x-pack/plugins/search_assistant/tsconfig.json @@ -16,11 +16,13 @@ "@kbn/react-kibana-context-render", "@kbn/kibana-react-plugin", "@kbn/i18n-react", - "@kbn/shared-ux-router", "@kbn/shared-ux-page-kibana-template", "@kbn/usage-collection-plugin", "@kbn/observability-ai-assistant-plugin", - "@kbn/config-schema" + "@kbn/config-schema", + "@kbn/ai-assistant", + "@kbn/i18n", + "@kbn/shared-ux-router" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/security/public/account_management/account_management_app.tsx b/x-pack/plugins/security/public/account_management/account_management_app.tsx index 6a812cf3ccb83..c224d5cbc40a1 100644 --- a/x-pack/plugins/security/public/account_management/account_management_app.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_app.tsx @@ -6,7 +6,8 @@ */ import type { History } from 'history'; -import React, { FC, PropsWithChildren } from 'react'; +import type { FC, PropsWithChildren } from 'react'; +import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import type { diff --git a/x-pack/plugins/security/public/authentication/components/authentication_state_page/authentication_state_page.tsx b/x-pack/plugins/security/public/authentication/components/authentication_state_page/authentication_state_page.tsx index eea9763420884..e491192b79d52 100644 --- a/x-pack/plugins/security/public/authentication/components/authentication_state_page/authentication_state_page.tsx +++ b/x-pack/plugins/security/public/authentication/components/authentication_state_page/authentication_state_page.tsx @@ -8,7 +8,8 @@ import './authentication_state_page.scss'; import { EuiIcon, EuiImage, EuiSpacer, EuiTitle } from '@elastic/eui'; -import React, { FC, PropsWithChildren } from 'react'; +import type { FC, PropsWithChildren } from 'react'; +import React from 'react'; interface Props { className?: string; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx index e74d2f7703f31..63c395b1f4bbc 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx @@ -5,11 +5,6 @@ * 2.0. */ -// brace/ace uses the Worker class, which is not currently provided by JSDOM. -// This is not required for the tests to pass, but it rather suppresses lengthy -// warnings in the console which adds unnecessary noise to the test output. -import '@kbn/web-worker-stub'; - import React from 'react'; import { coreMock, scopedHistoryMock } from '@kbn/core/public/mocks'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx index 64e332bd130bd..9db22a251779b 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx @@ -5,13 +5,6 @@ * 2.0. */ -import 'brace'; -import 'brace/mode/json'; -// brace/ace uses the Worker class, which is not currently provided by JSDOM. -// This is not required for the tests to pass, but it rather suppresses lengthy -// warnings in the console which adds unnecessary noise to the test output. -import '@kbn/web-worker-stub'; - import React from 'react'; import { act } from 'react-dom/test-utils'; import '@kbn/code-editor-mock/jest_helper'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx index 963bbf3a35cfc..121f694517a83 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx @@ -5,10 +5,6 @@ * 2.0. */ -import 'react-ace'; -import 'brace/mode/json'; -import 'brace/theme/github'; - import { EuiButton, EuiFormRow, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; import React, { Fragment, useState } from 'react'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx index d01229cdce8a9..21ece31571ae1 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx @@ -5,11 +5,6 @@ * 2.0. */ -// brace/ace uses the Worker class, which is not currently provided by JSDOM. -// This is not required for the tests to pass, but it rather suppresses lengthy -// warnings in the console which adds unnecessary noise to the test output. -import '@kbn/web-worker-stub'; - import { EuiErrorBoundary } from '@elastic/eui'; import React from 'react'; diff --git a/x-pack/plugins/security/public/management/users/users_management_app.tsx b/x-pack/plugins/security/public/management/users/users_management_app.tsx index aac45ae6c7934..eb005b68d812c 100644 --- a/x-pack/plugins/security/public/management/users/users_management_app.tsx +++ b/x-pack/plugins/security/public/management/users/users_management_app.tsx @@ -6,7 +6,8 @@ */ import type { History } from 'history'; -import React, { FC, PropsWithChildren } from 'react'; +import type { FC, PropsWithChildren } from 'react'; +import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Redirect } from 'react-router-dom'; diff --git a/x-pack/plugins/security/server/authorization/api_authorization.ts b/x-pack/plugins/security/server/authorization/api_authorization.ts index ba38d9ca0aa20..9c67ff8bdff8b 100644 --- a/x-pack/plugins/security/server/authorization/api_authorization.ts +++ b/x-pack/plugins/security/server/authorization/api_authorization.ts @@ -87,17 +87,17 @@ export function initAPIAuthorization( const missingPrivileges = Object.keys(kibanaPrivileges).filter( (key) => !kibanaPrivileges[key] ); - logger.warn( - `User not authorized for "${request.url.pathname}${ - request.url.search - }", responding with 403: missing privileges: ${missingPrivileges.join(', ')}` - ); + const forbiddenMessage = `API [${request.route.method.toUpperCase()} ${ + request.url.pathname + }${ + request.url.search + }] is unauthorized for user, this action is granted by the Kibana privileges [${missingPrivileges}]`; + + logger.warn(forbiddenMessage); return response.forbidden({ body: { - message: `User not authorized for ${request.url.pathname}${ - request.url.search - }, missing privileges: ${missingPrivileges.join(', ')}`, + message: forbiddenMessage, }, }); } diff --git a/x-pack/plugins/security/tsconfig.json b/x-pack/plugins/security/tsconfig.json index 535e221f8e5fb..2d5509d2d6d42 100644 --- a/x-pack/plugins/security/tsconfig.json +++ b/x-pack/plugins/security/tsconfig.json @@ -51,7 +51,6 @@ "@kbn/core-saved-objects-api-server-internal", "@kbn/core-saved-objects-api-server-mocks", "@kbn/logging-mocks", - "@kbn/web-worker-stub", "@kbn/core-saved-objects-utils-server", "@kbn/core-saved-objects-api-server", "@kbn/core-saved-objects-base-server-internal", diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 1ae20af759611..1e5ffee50afc7 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -138,11 +138,6 @@ export const allowedExperimentalValues = Object.freeze({ */ esqlRulesDisabled: false, - /** - * enables logging requests during rule preview - */ - loggingRequestsEnabled: false, - /** * Enables Protection Updates tab in the Endpoint Policy Details page */ diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx index a13a77a3562ff..a372ca4755fd8 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx @@ -35,7 +35,7 @@ const FIRST_RECORD_PAGINATION = { querySize: 1, }; -const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: number) => { +export const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: number) => { if (passedFindingsStats === 0 && failedFindingsStats === 0) return []; return [ { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.test.tsx index 4ebb460177476..25d5b90d5408a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.test.tsx @@ -23,7 +23,6 @@ import { stepDefineDefaultValue, } from '../../../../detections/pages/detection_engine/rules/utils'; import { usePreviewInvocationCount } from './use_preview_invocation_count'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; jest.mock('../../../../common/lib/kibana'); jest.mock('./use_preview_route'); @@ -40,7 +39,6 @@ jest.mock('../../../../common/hooks/use_experimental_features', () => ({ useIsExperimentalFeatureEnabled: jest.fn(), })); -const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; // rule types that do not support logged requests const doNotSupportLoggedRequests: Type[] = [ 'threshold', @@ -114,8 +112,6 @@ describe('PreviewQuery', () => { }); (usePreviewInvocationCount as jest.Mock).mockReturnValue({ invocationCount: 500 }); - - useIsExperimentalFeatureEnabledMock.mockReturnValue(true); }); afterEach(() => { @@ -172,23 +168,6 @@ describe('PreviewQuery', () => { }); }); - supportLoggedRequests.forEach((ruleType) => { - test(`does not render "Show Elasticsearch requests" for ${ruleType} rule type when feature is disabled`, () => { - useIsExperimentalFeatureEnabledMock.mockReturnValue(false); - - render( - - - - ); - - expect(screen.queryByTestId('show-elasticsearch-requests')).toBeNull(); - }); - }); - doNotSupportLoggedRequests.forEach((ruleType) => { test(`does not render "Show Elasticsearch requests" for ${ruleType} rule type`, () => { render( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.tsx index 2a86600d94e7a..f941cad91d3a4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/index.tsx @@ -40,7 +40,6 @@ import type { TimeframePreviewOptions, } from '../../../../detections/pages/detection_engine/rules/types'; import { usePreviewInvocationCount } from './use_preview_invocation_count'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; export const REASONABLE_INVOCATION_COUNT = 200; @@ -90,8 +89,6 @@ const RulePreviewComponent: React.FC = ({ const { indexPattern, ruleType } = defineRuleData; const { spaces } = useKibana().services; - const isLoggingRequestsFeatureEnabled = useIsExperimentalFeatureEnabled('loggingRequestsEnabled'); - const [spaceId, setSpaceId] = useState(''); useEffect(() => { if (spaces) { @@ -282,8 +279,7 @@ const RulePreviewComponent: React.FC = ({ - {isLoggingRequestsFeatureEnabled && - RULE_TYPES_SUPPORTING_LOGGED_REQUESTS.includes(ruleType) ? ( + {RULE_TYPES_SUPPORTING_LOGGED_REQUESTS.includes(ruleType) ? ( diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.test.tsx index 46288434f48bb..23f6969c36778 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.test.tsx @@ -7,6 +7,8 @@ import React from 'react'; import { render } from '@testing-library/react'; +import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; +import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; import type { Anomalies } from '../../../../common/components/ml/types'; import { DocumentDetailsContext } from '../../shared/context'; import { TestProviders } from '../../../../common/mock'; @@ -24,6 +26,9 @@ import { HOST_DETAILS_LINK_TEST_ID, HOST_DETAILS_RELATED_USERS_LINK_TEST_ID, HOST_DETAILS_RELATED_USERS_IP_LINK_TEST_ID, + HOST_DETAILS_MISCONFIGURATIONS_TEST_ID, + HOST_DETAILS_VULNERABILITIES_TEST_ID, + HOST_DETAILS_ALERT_COUNT_TEST_ID, } from './test_ids'; import { EXPANDABLE_PANEL_CONTENT_TEST_ID } from '@kbn/security-solution-common'; import { useRiskScore } from '../../../../entity_analytics/api/hooks/use_risk_score'; @@ -35,8 +40,11 @@ import { HOST_PREVIEW_BANNER } from '../../right/components/host_entity_overview import { UserPreviewPanelKey } from '../../../entity_details/user_right'; import { USER_PREVIEW_BANNER } from '../../right/components/user_entity_overview'; import { NetworkPanelKey, NETWORK_PREVIEW_BANNER } from '../../../network_details'; +import { useSummaryChartData } from '../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data'; jest.mock('@kbn/expandable-flyout'); +jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'); +jest.mock('@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'); jest.mock('../../../../common/hooks/use_experimental_features'); const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; @@ -104,6 +112,10 @@ const mockUseHostsRelatedUsers = useHostRelatedUsers as jest.Mock; jest.mock('../../../../entity_analytics/api/hooks/use_risk_score'); const mockUseRiskScore = useRiskScore as jest.Mock; +jest.mock( + '../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data' +); + const timestamp = '2022-07-25T08:20:18.966Z'; const defaultProps = { @@ -158,6 +170,9 @@ describe('', () => { mockUseRiskScore.mockReturnValue(mockRiskScoreResponse); mockUseHostsRelatedUsers.mockReturnValue(mockRelatedUsersResponse); mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); + (useMisconfigurationPreview as jest.Mock).mockReturnValue({}); + (useVulnerabilitiesPreview as jest.Mock).mockReturnValue({}); + (useSummaryChartData as jest.Mock).mockReturnValue({ isLoading: false, items: [] }); }); it('should render host details correctly', () => { @@ -296,4 +311,41 @@ describe('', () => { }); }); }); + + describe('distribution bar insights', () => { + it('should not render if no data is available', () => { + const { queryByTestId } = renderHostDetails(mockContextValue); + expect(queryByTestId(HOST_DETAILS_MISCONFIGURATIONS_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(HOST_DETAILS_VULNERABILITIES_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(HOST_DETAILS_ALERT_COUNT_TEST_ID)).not.toBeInTheDocument(); + }); + + it('should render alert count when data is available', () => { + (useSummaryChartData as jest.Mock).mockReturnValue({ + isLoading: false, + items: [{ key: 'high', value: 78, label: 'High' }], + }); + + const { getByTestId } = renderHostDetails(mockContextValue); + expect(getByTestId(HOST_DETAILS_ALERT_COUNT_TEST_ID)).toBeInTheDocument(); + }); + + it('should render misconfiguration when data is available', () => { + (useMisconfigurationPreview as jest.Mock).mockReturnValue({ + data: { count: { passed: 1, failed: 2 } }, + }); + + const { getByTestId } = renderHostDetails(mockContextValue); + expect(getByTestId(HOST_DETAILS_MISCONFIGURATIONS_TEST_ID)).toBeInTheDocument(); + }); + + it('should render vulnerabilities when data is available', () => { + (useVulnerabilitiesPreview as jest.Mock).mockReturnValue({ + data: { count: { CRITICAL: 0, HIGH: 1, MEDIUM: 1, LOW: 0, UNKNOWN: 0 } }, + }); + + const { getByTestId } = renderHostDetails(mockContextValue); + expect(getByTestId(HOST_DETAILS_VULNERABILITIES_TEST_ID)).toBeInTheDocument(); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx index 33b8bb22fce53..122caa657b039 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx @@ -18,6 +18,8 @@ import { EuiToolTip, EuiIcon, EuiPanel, + EuiHorizontalRule, + EuiFlexGrid, } from '@elastic/eui'; import type { EuiBasicTableColumn } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -51,6 +53,9 @@ import { HOST_DETAILS_RELATED_USERS_TABLE_TEST_ID, HOST_DETAILS_RELATED_USERS_LINK_TEST_ID, HOST_DETAILS_RELATED_USERS_IP_LINK_TEST_ID, + HOST_DETAILS_ALERT_COUNT_TEST_ID, + HOST_DETAILS_MISCONFIGURATIONS_TEST_ID, + HOST_DETAILS_VULNERABILITIES_TEST_ID, } from './test_ids'; import { USER_NAME_FIELD_NAME, @@ -63,6 +68,9 @@ import { PreviewLink } from '../../../shared/components/preview_link'; import { HostPreviewPanelKey } from '../../../entity_details/host_right'; import { HOST_PREVIEW_BANNER } from '../../right/components/host_entity_overview'; import type { NarrowDateRange } from '../../../../common/components/ml/types'; +import { MisconfigurationsInsight } from '../../shared/components/misconfiguration_insight'; +import { VulnerabilitiesInsight } from '../../shared/components/vulnerabilities_insight'; +import { AlertCountInsight } from '../../shared/components/alert_count_insight'; const HOST_DETAILS_ID = 'entities-hosts-details'; const RELATED_USERS_ID = 'entities-hosts-related-users'; @@ -337,6 +345,28 @@ export const HostDetails: React.FC = ({ hostName, timestamp, s )} + + + + + + + + diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/test_ids.ts index 0779f3c135b2d..8669b504f6861 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/test_ids.ts @@ -43,6 +43,9 @@ export const PREVALENCE_DETAILS_TABLE_UPSELL_CELL_TEST_ID = export const ENTITIES_DETAILS_TEST_ID = `${PREFIX}EntitiesDetails` as const; export const USER_DETAILS_TEST_ID = `${PREFIX}UsersDetails` as const; export const USER_DETAILS_LINK_TEST_ID = `${USER_DETAILS_TEST_ID}TitleLink` as const; +export const USER_DETAILS_ALERT_COUNT_TEST_ID = `${USER_DETAILS_TEST_ID}AlertCount` as const; +export const USER_DETAILS_MISCONFIGURATIONS_TEST_ID = + `${USER_DETAILS_TEST_ID}Misconfigurations` as const; export const USER_DETAILS_RELATED_HOSTS_TABLE_TEST_ID = `${USER_DETAILS_TEST_ID}RelatedHostsTable` as const; export const USER_DETAILS_RELATED_HOSTS_LINK_TEST_ID = @@ -53,6 +56,11 @@ export const USER_DETAILS_INFO_TEST_ID = 'user-overview' as const; export const HOST_DETAILS_TEST_ID = `${PREFIX}HostsDetails` as const; export const HOST_DETAILS_LINK_TEST_ID = `${HOST_DETAILS_TEST_ID}TitleLink` as const; +export const HOST_DETAILS_ALERT_COUNT_TEST_ID = `${HOST_DETAILS_TEST_ID}AlertCount` as const; +export const HOST_DETAILS_MISCONFIGURATIONS_TEST_ID = + `${HOST_DETAILS_TEST_ID}Misconfigurations` as const; +export const HOST_DETAILS_VULNERABILITIES_TEST_ID = + `${HOST_DETAILS_TEST_ID}Vulnerabilities` as const; export const HOST_DETAILS_RELATED_USERS_TABLE_TEST_ID = `${HOST_DETAILS_TEST_ID}RelatedUsersTable` as const; export const HOST_DETAILS_RELATED_USERS_LINK_TEST_ID = diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.test.tsx index c1ed881e80a95..a2c53afb8c3f3 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render } from '@testing-library/react'; +import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; import type { Anomalies } from '../../../../common/components/ml/types'; import { TestProviders } from '../../../../common/mock'; import { DocumentDetailsContext } from '../../shared/context'; @@ -24,6 +25,8 @@ import { USER_DETAILS_RELATED_HOSTS_TABLE_TEST_ID, USER_DETAILS_RELATED_HOSTS_LINK_TEST_ID, USER_DETAILS_RELATED_HOSTS_IP_LINK_TEST_ID, + USER_DETAILS_MISCONFIGURATIONS_TEST_ID, + USER_DETAILS_ALERT_COUNT_TEST_ID, } from './test_ids'; import { EXPANDABLE_PANEL_CONTENT_TEST_ID } from '@kbn/security-solution-common'; import { useRiskScore } from '../../../../entity_analytics/api/hooks/use_risk_score'; @@ -35,8 +38,10 @@ import { HOST_PREVIEW_BANNER } from '../../right/components/host_entity_overview import { UserPreviewPanelKey } from '../../../entity_details/user_right'; import { USER_PREVIEW_BANNER } from '../../right/components/user_entity_overview'; import { NetworkPanelKey, NETWORK_PREVIEW_BANNER } from '../../../network_details'; +import { useSummaryChartData } from '../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data'; jest.mock('@kbn/expandable-flyout'); +jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'); jest.mock('../../../../common/hooks/use_experimental_features'); const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; @@ -101,6 +106,10 @@ const mockUseUsersRelatedHosts = useUserRelatedHosts as jest.Mock; jest.mock('../../../../entity_analytics/api/hooks/use_risk_score'); const mockUseRiskScore = useRiskScore as jest.Mock; +jest.mock( + '../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data' +); + const timestamp = '2022-07-25T08:20:18.966Z'; const defaultProps = { @@ -155,6 +164,8 @@ describe('', () => { mockUseRiskScore.mockReturnValue(mockRiskScoreResponse); mockUseUsersRelatedHosts.mockReturnValue(mockRelatedHostsResponse); mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); + (useMisconfigurationPreview as jest.Mock).mockReturnValue({}); + (useSummaryChartData as jest.Mock).mockReturnValue({ isLoading: false, items: [] }); }); it('should render user details correctly', () => { @@ -278,4 +289,31 @@ describe('', () => { }); }); }); + + describe('distribution bar insights', () => { + it('should not render if no data is available', () => { + const { queryByTestId } = renderUserDetails(mockContextValue); + expect(queryByTestId(USER_DETAILS_MISCONFIGURATIONS_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(USER_DETAILS_ALERT_COUNT_TEST_ID)).not.toBeInTheDocument(); + }); + + it('should render alert count when data is available', () => { + (useSummaryChartData as jest.Mock).mockReturnValue({ + isLoading: false, + items: [{ key: 'high', value: 78, label: 'High' }], + }); + + const { getByTestId } = renderUserDetails(mockContextValue); + expect(getByTestId(USER_DETAILS_ALERT_COUNT_TEST_ID)).toBeInTheDocument(); + }); + + it('should render misconfiguration when data is available', () => { + (useMisconfigurationPreview as jest.Mock).mockReturnValue({ + data: { count: { passed: 1, failed: 2 } }, + }); + + const { getByTestId } = renderUserDetails(mockContextValue); + expect(getByTestId(USER_DETAILS_MISCONFIGURATIONS_TEST_ID)).toBeInTheDocument(); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx index 13d3e825053ba..c90d11f4b8bc2 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx @@ -18,6 +18,8 @@ import { EuiFlexItem, EuiToolTip, EuiPanel, + EuiHorizontalRule, + EuiFlexGrid, } from '@elastic/eui'; import type { EuiBasicTableColumn } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -51,6 +53,8 @@ import { USER_DETAILS_TEST_ID, USER_DETAILS_RELATED_HOSTS_LINK_TEST_ID, USER_DETAILS_RELATED_HOSTS_IP_LINK_TEST_ID, + USER_DETAILS_MISCONFIGURATIONS_TEST_ID, + USER_DETAILS_ALERT_COUNT_TEST_ID, } from './test_ids'; import { HOST_NAME_FIELD_NAME, @@ -63,6 +67,8 @@ import { UserPreviewPanelKey } from '../../../entity_details/user_right'; import { USER_PREVIEW_BANNER } from '../../right/components/user_entity_overview'; import { PreviewLink } from '../../../shared/components/preview_link'; import type { NarrowDateRange } from '../../../../common/components/ml/types'; +import { MisconfigurationsInsight } from '../../shared/components/misconfiguration_insight'; +import { AlertCountInsight } from '../../shared/components/alert_count_insight'; const USER_DETAILS_ID = 'entities-users-details'; const RELATED_HOSTS_ID = 'entities-users-related-hosts'; @@ -340,6 +346,22 @@ export const UserDetails: React.FC = ({ userName, timestamp, s )} + + + + + + diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.test.tsx index b710df84e1a13..6ad90adb28997 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.test.tsx @@ -6,6 +6,8 @@ */ import React from 'react'; import { render } from '@testing-library/react'; +import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; +import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; import { TestProviders } from '../../../../common/mock'; import { HostEntityOverview, HOST_PREVIEW_BANNER } from './host_entity_overview'; import { useHostDetails } from '../../../../explore/hosts/containers/hosts/details'; @@ -16,6 +18,9 @@ import { ENTITIES_HOST_OVERVIEW_LINK_TEST_ID, ENTITIES_HOST_OVERVIEW_RISK_LEVEL_TEST_ID, ENTITIES_HOST_OVERVIEW_LOADING_TEST_ID, + ENTITIES_HOST_OVERVIEW_MISCONFIGURATIONS_TEST_ID, + ENTITIES_HOST_OVERVIEW_VULNERABILITIES_TEST_ID, + ENTITIES_HOST_OVERVIEW_ALERT_COUNT_TEST_ID, } from './test_ids'; import { DocumentDetailsContext } from '../../shared/context'; import { mockContextValue } from '../../shared/mocks/mock_context'; @@ -29,6 +34,7 @@ import { ENTITIES_TAB_ID } from '../../left/components/entities_details'; import { useRiskScore } from '../../../../entity_analytics/api/hooks/use_risk_score'; import { mockFlyoutApi } from '../../shared/mocks/mock_flyout_context'; import { createTelemetryServiceMock } from '../../../../common/lib/telemetry/telemetry_service.mock'; +import { useSummaryChartData } from '../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data'; const hostName = 'host'; const osFamily = 'Windows'; @@ -46,6 +52,17 @@ const panelContextValue = { }; jest.mock('@kbn/expandable-flyout'); +jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'); +jest.mock('@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'); + +jest.mock('react-router-dom', () => { + const actual = jest.requireActual('react-router-dom'); + return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; +}); + +jest.mock( + '../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data' +); const mockedTelemetry = createTelemetryServiceMock(); jest.mock('../../../../common/lib/kibana', () => { @@ -99,6 +116,9 @@ describe('', () => { beforeAll(() => { jest.mocked(useExpandableFlyoutApi).mockReturnValue(mockFlyoutApi); mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); + (useMisconfigurationPreview as jest.Mock).mockReturnValue({}); + (useVulnerabilitiesPreview as jest.Mock).mockReturnValue({}); + (useSummaryChartData as jest.Mock).mockReturnValue({ isLoading: false, items: [] }); }); describe('license is valid', () => { @@ -150,6 +170,7 @@ describe('', () => { ); expect(getByTestId(ENTITIES_HOST_OVERVIEW_LOADING_TEST_ID)).toBeInTheDocument(); }); + describe('license is not valid', () => { it('should render os family and last seen', () => { mockUseHostDetails.mockReturnValue([false, { hostDetails: hostData }]); @@ -210,4 +231,48 @@ describe('', () => { }); }); }); + + describe('distribution bar insights', () => { + beforeEach(() => { + mockUseHostDetails.mockReturnValue([false, { hostDetails: hostData }]); + mockUseRiskScore.mockReturnValue({ data: riskLevel, isAuthorized: true }); + }); + + it('should not render if no data is available', () => { + const { queryByTestId } = renderHostEntityContent(); + expect( + queryByTestId(ENTITIES_HOST_OVERVIEW_MISCONFIGURATIONS_TEST_ID) + ).not.toBeInTheDocument(); + expect(queryByTestId(ENTITIES_HOST_OVERVIEW_VULNERABILITIES_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(ENTITIES_HOST_OVERVIEW_ALERT_COUNT_TEST_ID)).not.toBeInTheDocument(); + }); + + it('should render alert count when data is available', () => { + (useSummaryChartData as jest.Mock).mockReturnValue({ + isLoading: false, + items: [{ key: 'high', value: 78, label: 'High' }], + }); + + const { getByTestId } = renderHostEntityContent(); + expect(getByTestId(ENTITIES_HOST_OVERVIEW_ALERT_COUNT_TEST_ID)).toBeInTheDocument(); + }); + + it('should render misconfiguration when data is available', () => { + (useMisconfigurationPreview as jest.Mock).mockReturnValue({ + data: { count: { passed: 1, failed: 2 } }, + }); + + const { getByTestId } = renderHostEntityContent(); + expect(getByTestId(ENTITIES_HOST_OVERVIEW_MISCONFIGURATIONS_TEST_ID)).toBeInTheDocument(); + }); + + it('should render vulnerabilities when data is available', () => { + (useVulnerabilitiesPreview as jest.Mock).mockReturnValue({ + data: { count: { CRITICAL: 0, HIGH: 1, MEDIUM: 1, LOW: 0, UNKNOWN: 0 } }, + }); + + const { getByTestId } = renderHostEntityContent(); + expect(getByTestId(ENTITIES_HOST_OVERVIEW_VULNERABILITIES_TEST_ID)).toBeInTheDocument(); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx index ca6a68eb23be8..90405286b004c 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx @@ -52,11 +52,17 @@ import { ENTITIES_HOST_OVERVIEW_RISK_LEVEL_TEST_ID, ENTITIES_HOST_OVERVIEW_LINK_TEST_ID, ENTITIES_HOST_OVERVIEW_LOADING_TEST_ID, + ENTITIES_HOST_OVERVIEW_ALERT_COUNT_TEST_ID, + ENTITIES_HOST_OVERVIEW_MISCONFIGURATIONS_TEST_ID, + ENTITIES_HOST_OVERVIEW_VULNERABILITIES_TEST_ID, } from './test_ids'; import { DocumentDetailsLeftPanelKey } from '../../shared/constants/panel_keys'; import { LeftPanelInsightsTab } from '../../left'; import { RiskScoreDocTooltip } from '../../../../overview/components/common'; import { PreviewLink } from '../../../shared/components/preview_link'; +import { MisconfigurationsInsight } from '../../shared/components/misconfiguration_insight'; +import { VulnerabilitiesInsight } from '../../shared/components/vulnerabilities_insight'; +import { AlertCountInsight } from '../../shared/components/alert_count_insight'; const HOST_ICON = 'storage'; @@ -196,12 +202,12 @@ export const HostEntityOverview: React.FC = ({ hostName return ( - + @@ -270,6 +276,20 @@ export const HostEntityOverview: React.FC = ({ hostName )} + + + ); }; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts index 40670ddc7110a..e0d8bc6db0f5c 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts @@ -121,6 +121,10 @@ export const ENTITIES_USER_OVERVIEW_LAST_SEEN_TEST_ID = `${ENTITIES_USER_OVERVIEW_TEST_ID}LastSeen` as const; export const ENTITIES_USER_OVERVIEW_RISK_LEVEL_TEST_ID = `${ENTITIES_USER_OVERVIEW_TEST_ID}RiskLevel` as const; +export const ENTITIES_USER_OVERVIEW_ALERT_COUNT_TEST_ID = + `${ENTITIES_USER_OVERVIEW_TEST_ID}AlertCount` as const; +export const ENTITIES_USER_OVERVIEW_MISCONFIGURATIONS_TEST_ID = + `${ENTITIES_USER_OVERVIEW_TEST_ID}Misconfigurations` as const; export const ENTITIES_HOST_OVERVIEW_TEST_ID = `${INSIGHTS_ENTITIES_TEST_ID}HostOverview` as const; export const ENTITIES_HOST_OVERVIEW_LOADING_TEST_ID = @@ -132,6 +136,12 @@ export const ENTITIES_HOST_OVERVIEW_LAST_SEEN_TEST_ID = `${ENTITIES_HOST_OVERVIEW_TEST_ID}LastSeen` as const; export const ENTITIES_HOST_OVERVIEW_RISK_LEVEL_TEST_ID = `${ENTITIES_HOST_OVERVIEW_TEST_ID}RiskLevel` as const; +export const ENTITIES_HOST_OVERVIEW_ALERT_COUNT_TEST_ID = + `${ENTITIES_HOST_OVERVIEW_TEST_ID}AlertCount` as const; +export const ENTITIES_HOST_OVERVIEW_MISCONFIGURATIONS_TEST_ID = + `${ENTITIES_HOST_OVERVIEW_TEST_ID}Misconfigurations` as const; +export const ENTITIES_HOST_OVERVIEW_VULNERABILITIES_TEST_ID = + `${ENTITIES_HOST_OVERVIEW_TEST_ID}Vulnerabilities` as const; /* Threat intelligence */ diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.test.tsx index 000da8946ff61..95c399ca4362e 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock'; +import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; import { UserEntityOverview, USER_PREVIEW_BANNER } from './user_entity_overview'; import { useFirstLastSeen } from '../../../../common/containers/use_first_last_seen'; import { @@ -15,6 +16,8 @@ import { ENTITIES_USER_OVERVIEW_LINK_TEST_ID, ENTITIES_USER_OVERVIEW_RISK_LEVEL_TEST_ID, ENTITIES_USER_OVERVIEW_LOADING_TEST_ID, + ENTITIES_USER_OVERVIEW_MISCONFIGURATIONS_TEST_ID, + ENTITIES_USER_OVERVIEW_ALERT_COUNT_TEST_ID, } from './test_ids'; import { useObservedUserDetails } from '../../../../explore/users/containers/users/observed_details'; import { mockContextValue } from '../../shared/mocks/mock_context'; @@ -28,6 +31,7 @@ import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { mockFlyoutApi } from '../../shared/mocks/mock_flyout_context'; import { UserPreviewPanelKey } from '../../../entity_details/user_right'; +import { useSummaryChartData } from '../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data'; const userName = 'user'; const domain = 'n54bg2lfc7'; @@ -45,6 +49,18 @@ const panelContextValue = { }; jest.mock('@kbn/expandable-flyout'); +jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'); + +jest.mock('../../../../common/lib/kibana'); + +jest.mock('react-router-dom', () => { + const actual = jest.requireActual('react-router-dom'); + return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; +}); + +jest.mock( + '../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data' +); jest.mock('../../../../common/hooks/use_experimental_features'); const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; @@ -85,6 +101,8 @@ describe('', () => { beforeAll(() => { jest.mocked(useExpandableFlyoutApi).mockReturnValue(mockFlyoutApi); mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); + (useMisconfigurationPreview as jest.Mock).mockReturnValue({}); + (useSummaryChartData as jest.Mock).mockReturnValue({ isLoading: false, items: [] }); }); describe('license is valid', () => { @@ -211,4 +229,38 @@ describe('', () => { }); }); }); + + describe('distribution bar insights', () => { + beforeEach(() => { + mockUseUserDetails.mockReturnValue([false, { userDetails: userData }]); + mockUseRiskScore.mockReturnValue({ data: riskLevel, isAuthorized: true }); + }); + + it('should not render if no data is available', () => { + const { queryByTestId } = renderUserEntityOverview(); + expect( + queryByTestId(ENTITIES_USER_OVERVIEW_MISCONFIGURATIONS_TEST_ID) + ).not.toBeInTheDocument(); + expect(queryByTestId(ENTITIES_USER_OVERVIEW_ALERT_COUNT_TEST_ID)).not.toBeInTheDocument(); + }); + + it('should render alert count when data is available', () => { + (useSummaryChartData as jest.Mock).mockReturnValue({ + isLoading: false, + items: [{ key: 'high', value: 78, label: 'High' }], + }); + + const { getByTestId } = renderUserEntityOverview(); + expect(getByTestId(ENTITIES_USER_OVERVIEW_ALERT_COUNT_TEST_ID)).toBeInTheDocument(); + }); + + it('should render misconfiguration when data is available', () => { + (useMisconfigurationPreview as jest.Mock).mockReturnValue({ + data: { count: { passed: 1, failed: 2 } }, + }); + + const { getByTestId } = renderUserEntityOverview(); + expect(getByTestId(ENTITIES_USER_OVERVIEW_MISCONFIGURATIONS_TEST_ID)).toBeInTheDocument(); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx index 624b9e816c9e5..0019228d656cd 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx @@ -53,10 +53,14 @@ import { ENTITIES_USER_OVERVIEW_RISK_LEVEL_TEST_ID, ENTITIES_USER_OVERVIEW_LINK_TEST_ID, ENTITIES_USER_OVERVIEW_LOADING_TEST_ID, + ENTITIES_USER_OVERVIEW_MISCONFIGURATIONS_TEST_ID, + ENTITIES_USER_OVERVIEW_ALERT_COUNT_TEST_ID, } from './test_ids'; import { useObservedUserDetails } from '../../../../explore/users/containers/users/observed_details'; import { RiskScoreDocTooltip } from '../../../../overview/components/common'; import { PreviewLink } from '../../../shared/components/preview_link'; +import { MisconfigurationsInsight } from '../../shared/components/misconfiguration_insight'; +import { AlertCountInsight } from '../../shared/components/alert_count_insight'; const USER_ICON = 'user'; @@ -196,12 +200,12 @@ export const UserEntityOverview: React.FC = ({ userName return ( - + @@ -270,6 +274,16 @@ export const UserEntityOverview: React.FC = ({ userName )} + + ); }; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/alert_count_insight.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/alert_count_insight.test.tsx new file mode 100644 index 0000000000000..f0d16a418f2b2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/alert_count_insight.test.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 { render } from '@testing-library/react'; +import { TestProviders } from '../../../../common/mock'; +import { AlertCountInsight } from './alert_count_insight'; +import { useSummaryChartData } from '../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data'; + +jest.mock('../../../../common/lib/kibana'); + +jest.mock('react-router-dom', () => { + const actual = jest.requireActual('react-router-dom'); + return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; +}); +jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'); +jest.mock( + '../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data' +); + +const fieldName = 'host.name'; +const name = 'test host'; +const testId = 'test'; + +const renderAlertCountInsight = () => { + return render( + + + + ); +}; + +describe('AlertCountInsight', () => { + it('renders', () => { + (useSummaryChartData as jest.Mock).mockReturnValue({ + isLoading: false, + items: [ + { key: 'high', value: 78, label: 'High' }, + { key: 'low', value: 46, label: 'Low' }, + { key: 'medium', value: 32, label: 'Medium' }, + { key: 'critical', value: 21, label: 'Critical' }, + ], + }); + const { getByTestId } = renderAlertCountInsight(); + expect(getByTestId(testId)).toBeInTheDocument(); + expect(getByTestId(`${testId}-distribution-bar`)).toBeInTheDocument(); + }); + + it('renders loading spinner if data is being fetched', () => { + (useSummaryChartData as jest.Mock).mockReturnValue({ isLoading: true, items: [] }); + const { getByTestId } = renderAlertCountInsight(); + expect(getByTestId(`${testId}-loading-spinner`)).toBeInTheDocument(); + }); + + it('renders null if no misconfiguration data found', () => { + (useSummaryChartData as jest.Mock).mockReturnValue({ isLoading: false, items: [] }); + const { container } = renderAlertCountInsight(); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/alert_count_insight.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/alert_count_insight.tsx new file mode 100644 index 0000000000000..566b77b5739a9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/alert_count_insight.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, { useMemo } from 'react'; +import { v4 as uuid } from 'uuid'; +import { EuiLoadingSpinner, EuiFlexItem, type EuiFlexGroupProps } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { InsightDistributionBar } from './insight_distribution_bar'; +import { severityAggregations } from '../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/aggregations'; +import { useSummaryChartData } from '../../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data'; +import { + getIsAlertsBySeverityData, + getSeverityColor, +} from '../../../../detections/components/alerts_kpis/severity_level_panel/helpers'; + +const ENTITY_ALERT_COUNT_ID = 'entity-alert-count'; + +interface AlertCountInsightProps { + /** + * The name of the entity to filter the alerts by. + */ + name: string; + /** + * The field name to filter the alerts by. + */ + fieldName: 'host.name' | 'user.name'; + /** + * The direction of the flex group. + */ + direction?: EuiFlexGroupProps['direction']; + /** + * The data-test-subj to use for the component. + */ + ['data-test-subj']?: string; +} + +/* + * Displays a distribution bar with the count of critical alerts for a given entity + */ +export const AlertCountInsight: React.FC = ({ + name, + fieldName, + direction, + 'data-test-subj': dataTestSubj, +}) => { + const uniqueQueryId = useMemo(() => `${ENTITY_ALERT_COUNT_ID}-${uuid()}`, []); + const entityFilter = useMemo(() => ({ field: fieldName, value: name }), [fieldName, name]); + + const { items, isLoading } = useSummaryChartData({ + aggregations: severityAggregations, + entityFilter, + uniqueQueryId, + signalIndexName: null, + }); + + const data = useMemo(() => (getIsAlertsBySeverityData(items) ? items : []), [items]); + + const alertStats = useMemo(() => { + return data.map((item) => ({ + key: item.key, + count: item.value, + color: getSeverityColor(item.key), + })); + }, [data]); + + const count = useMemo( + () => data.filter((item) => item.key === 'critical')[0]?.value ?? 0, + [data] + ); + + if (!isLoading && items.length === 0) return null; + + return ( + + {isLoading ? ( + + ) : ( + + } + stats={alertStats} + count={count} + direction={direction} + data-test-subj={`${dataTestSubj}-distribution-bar`} + /> + )} + + ); +}; + +AlertCountInsight.displayName = 'AlertCountInsight'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/insight_distribution_bar.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/insight_distribution_bar.test.tsx new file mode 100644 index 0000000000000..a775da8a7f73a --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/insight_distribution_bar.test.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 from 'react'; +import { render } from '@testing-library/react'; +import { InsightDistributionBar } from './insight_distribution_bar'; +import { TestProviders } from '../../../../common/mock'; + +const title = 'test title'; +const count = 10; +const testId = 'test-id'; +const stats = [ + { + key: 'passed', + count: 90, + color: 'green', + }, + { + key: 'failed', + count: 10, + color: 'red', + }, +]; + +describe('', () => { + it('should render', () => { + const { getByTestId, getByText } = render( + + + + ); + expect(getByTestId(testId)).toBeInTheDocument(); + expect(getByText(title)).toBeInTheDocument(); + expect(getByTestId(`${testId}-badge`)).toHaveTextContent(`${count}`); + expect(getByTestId(`${testId}-distribution-bar`)).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/insight_distribution_bar.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/insight_distribution_bar.tsx new file mode 100644 index 0000000000000..006ec8c5dad4f --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/insight_distribution_bar.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 { css } from '@emotion/css'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiBadge, + useEuiTheme, + useEuiFontSize, + type EuiFlexGroupProps, +} from '@elastic/eui'; +import { DistributionBar } from '@kbn/security-solution-distribution-bar'; +import { FormattedCount } from '../../../../common/components/formatted_number'; + +export interface InsightDistributionBarProps { + /** + * Title of the insight + */ + title: string | React.ReactNode; + /** + * Distribution stats to display + */ + stats: Array<{ key: string; count: number; color: string; label?: React.ReactNode }>; + /** + * Count to be displayed in the badge + */ + count: number; + /** + * Flex direction of the component + */ + direction?: EuiFlexGroupProps['direction']; + /** + * Optional test id + */ + ['data-test-subj']?: string; +} + +// Displays a distribution bar with a count badge +export const InsightDistributionBar: React.FC = ({ + title, + stats, + count, + direction = 'row', + 'data-test-subj': dataTestSubj, +}) => { + const { euiTheme } = useEuiTheme(); + const xsFontSize = useEuiFontSize('xs').fontSize; + + return ( + + + + {title} + + + + + + + + + + + + + + + + ); +}; + +InsightDistributionBar.displayName = 'InsightDistributionBar'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.test.tsx new file mode 100644 index 0000000000000..296a61f444a17 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { TestProviders } from '../../../../common/mock'; +import { MisconfigurationsInsight } from './misconfiguration_insight'; +import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; + +jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'); + +const fieldName = 'host.name'; +const name = 'test host'; +const testId = 'test'; + +const renderMisconfigurationsInsight = () => { + return render( + + + + ); +}; + +describe('MisconfigurationsInsight', () => { + it('renders', () => { + (useMisconfigurationPreview as jest.Mock).mockReturnValue({ + data: { count: { passed: 1, failed: 2 } }, + }); + const { getByTestId } = renderMisconfigurationsInsight(); + expect(getByTestId(testId)).toBeInTheDocument(); + expect(getByTestId(`${testId}-distribution-bar`)).toBeInTheDocument(); + }); + + it('renders null if no misconfiguration data found', () => { + (useMisconfigurationPreview as jest.Mock).mockReturnValue({}); + const { container } = renderMisconfigurationsInsight(); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.tsx new file mode 100644 index 0000000000000..552a242c84893 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { EuiFlexItem, type EuiFlexGroupProps } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; +import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; +import { InsightDistributionBar } from './insight_distribution_bar'; +import { getFindingsStats } from '../../../../cloud_security_posture/components/misconfiguration/misconfiguration_preview'; + +interface MisconfigurationsInsightProps { + /** + * Entity name to retrieve misconfigurations for + */ + name: string; + /** + * Indicator whether the entity is host or user + */ + fieldName: 'host.name' | 'user.name'; + /** + * The direction of the flex group + */ + direction?: EuiFlexGroupProps['direction']; + /** + * The data-test-subj to use for the component + */ + ['data-test-subj']?: string; +} + +/* + * Displays a distribution bar with the count of failed misconfigurations for a given entity + */ +export const MisconfigurationsInsight: React.FC = ({ + name, + fieldName, + direction, + 'data-test-subj': dataTestSubj, +}) => { + const { data } = useMisconfigurationPreview({ + query: buildEntityFlyoutPreviewQuery(fieldName, name), + sort: [], + enabled: true, + pageSize: 1, + }); + + const passedFindings = data?.count.passed || 0; + const failedFindings = data?.count.failed || 0; + const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0; + + const misconfigurationsStats = useMemo( + () => getFindingsStats(passedFindings, failedFindings), + [passedFindings, failedFindings] + ); + + if (!hasMisconfigurationFindings) return null; + + return ( + + + } + stats={misconfigurationsStats} + count={failedFindings} + direction={direction} + data-test-subj={`${dataTestSubj}-distribution-bar`} + /> + + ); +}; + +MisconfigurationsInsight.displayName = 'MisconfigurationsInsight'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/test_ids.ts index 8561df63d7199..7c2ce2ff5870b 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/test_ids.ts @@ -12,3 +12,6 @@ export const FLYOUT_PREVIEW_LINK_TEST_ID = `${PREFIX}PreviewLink` as const; export const SESSION_VIEW_UPSELL_TEST_ID = `${PREFIX}SessionViewUpsell` as const; export const SESSION_VIEW_NO_DATA_TEST_ID = `${PREFIX}SessionViewNoData` as const; + +export const MISCONFIGURATIONS_TEST_ID = `${PREFIX}Misconfigurations` as const; +export const VULNERABILITIES_TEST_ID = `${PREFIX}Vulnerabilities` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.test.tsx new file mode 100644 index 0000000000000..77c6737266b89 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.test.tsx @@ -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 { TestProviders } from '../../../../common/mock'; +import { render } from '@testing-library/react'; +import React from 'react'; +import { VulnerabilitiesInsight } from './vulnerabilities_insight'; +import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; + +jest.mock('@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'); + +const hostName = 'test host'; +const testId = 'test'; + +const renderVulnerabilitiesInsight = () => { + return render( + + + + ); +}; + +describe('VulnerabilitiesInsight', () => { + it('renders', () => { + (useVulnerabilitiesPreview as jest.Mock).mockReturnValue({ + data: { count: { CRITICAL: 0, HIGH: 1, MEDIUM: 1, LOW: 0, UNKNOWN: 0 } }, + }); + + const { getByTestId } = renderVulnerabilitiesInsight(); + expect(getByTestId(testId)).toBeInTheDocument(); + expect(getByTestId(`${testId}-distribution-bar`)).toBeInTheDocument(); + }); + + it('renders null when data is not available', () => { + (useVulnerabilitiesPreview as jest.Mock).mockReturnValue({}); + + const { container } = renderVulnerabilitiesInsight(); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx new file mode 100644 index 0000000000000..4c581b6db57d0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { EuiFlexItem, type EuiFlexGroupProps } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; +import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; +import { getVulnerabilityStats, hasVulnerabilitiesData } from '@kbn/cloud-security-posture'; +import { InsightDistributionBar } from './insight_distribution_bar'; + +interface VulnerabilitiesInsightProps { + /** + * Host name to retrieve vulnerabilities for + */ + hostName: string; + /** + * The direction of the flex group + */ + direction?: EuiFlexGroupProps['direction']; + /** + * The data-test-subj to use for the component + */ + ['data-test-subj']?: string; +} + +/* + * Displays a distribution bar with the count of critical vulnerabilities for a given host + */ +export const VulnerabilitiesInsight: React.FC = ({ + hostName, + direction, + 'data-test-subj': dataTestSubj, +}) => { + const { data } = useVulnerabilitiesPreview({ + query: buildEntityFlyoutPreviewQuery('host.name', hostName), + sort: [], + enabled: true, + pageSize: 1, + }); + + const { CRITICAL = 0, HIGH = 0, MEDIUM = 0, LOW = 0, NONE = 0 } = data?.count || {}; + const hasVulnerabilitiesFindings = useMemo( + () => + hasVulnerabilitiesData({ + critical: CRITICAL, + high: HIGH, + medium: MEDIUM, + low: LOW, + none: NONE, + }), + [CRITICAL, HIGH, MEDIUM, LOW, NONE] + ); + + const vulnerabilitiesStats = useMemo( + () => + getVulnerabilityStats({ + critical: CRITICAL, + high: HIGH, + medium: MEDIUM, + low: LOW, + none: NONE, + }), + [CRITICAL, HIGH, MEDIUM, LOW, NONE] + ); + + if (!hasVulnerabilitiesFindings) return null; + + return ( + + + } + stats={vulnerabilitiesStats} + count={CRITICAL} + direction={direction} + data-test-subj={`${dataTestSubj}-distribution-bar`} + /> + + ); +}; + +VulnerabilitiesInsight.displayName = 'VulnerabilitiesInsight'; diff --git a/x-pack/plugins/security_solution/public/flyout/rule_details/preview/footer.test.tsx b/x-pack/plugins/security_solution/public/flyout/rule_details/preview/footer.test.tsx index f1e276011ca26..0f2a7dc74662f 100644 --- a/x-pack/plugins/security_solution/public/flyout/rule_details/preview/footer.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/rule_details/preview/footer.test.tsx @@ -9,20 +9,21 @@ import { render } from '@testing-library/react'; import React from 'react'; import { RULE_PREVIEW_FOOTER_TEST_ID, RULE_PREVIEW_OPEN_RULE_FLYOUT_TEST_ID } from './test_ids'; import { PreviewFooter } from './footer'; -import { mockFlyoutApi } from '../../document_details/shared/mocks/mock_flyout_context'; -import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; -import { RulePanelKey } from '../right'; +import { useRuleDetailsLink } from '../../document_details/shared/hooks/use_rule_details_link'; +import { TestProviders } from '../../../common/mock'; -jest.mock('@kbn/expandable-flyout'); +jest.mock('../../document_details/shared/hooks/use_rule_details_link'); -const renderRulePreviewFooter = () => render(); +const renderRulePreviewFooter = () => + render( + + + + ); describe('', () => { - beforeAll(() => { - jest.mocked(useExpandableFlyoutApi).mockReturnValue(mockFlyoutApi); - }); - it('should render rule details link correctly when ruleId is available', () => { + (useRuleDetailsLink as jest.Mock).mockReturnValue('rule_details_link'); const { getByTestId } = renderRulePreviewFooter(); expect(getByTestId(RULE_PREVIEW_FOOTER_TEST_ID)).toBeInTheDocument(); @@ -32,13 +33,9 @@ describe('', () => { ); }); - it('should open rule flyout when clicked', () => { - const { getByTestId } = renderRulePreviewFooter(); - - getByTestId(RULE_PREVIEW_OPEN_RULE_FLYOUT_TEST_ID).click(); - - expect(mockFlyoutApi.openFlyout).toHaveBeenCalledWith({ - right: { id: RulePanelKey, params: { ruleId: 'ruleid' } }, - }); + it('should not render the footer if rule link is not available', () => { + (useRuleDetailsLink as jest.Mock).mockReturnValue(null); + const { container } = renderRulePreviewFooter(); + expect(container).toBeEmptyDOMElement(); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/rule_details/preview/footer.tsx b/x-pack/plugins/security_solution/public/flyout/rule_details/preview/footer.tsx index 1774c37d9e535..42c8c1a6d85b9 100644 --- a/x-pack/plugins/security_solution/public/flyout/rule_details/preview/footer.tsx +++ b/x-pack/plugins/security_solution/public/flyout/rule_details/preview/footer.tsx @@ -5,38 +5,27 @@ * 2.0. */ -import React, { memo, useCallback } from 'react'; +import React, { memo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FlyoutFooter } from '@kbn/security-solution-common'; -import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { RULE_PREVIEW_FOOTER_TEST_ID, RULE_PREVIEW_OPEN_RULE_FLYOUT_TEST_ID } from './test_ids'; -import { RulePanelKey } from '../right'; +import { useRuleDetailsLink } from '../../document_details/shared/hooks/use_rule_details_link'; /** * Footer in rule preview panel */ export const PreviewFooter = memo(({ ruleId }: { ruleId: string }) => { - const { openFlyout } = useExpandableFlyoutApi(); + const href = useRuleDetailsLink({ ruleId }); - const openRuleFlyout = useCallback(() => { - openFlyout({ - right: { - id: RulePanelKey, - params: { - ruleId, - }, - }, - }); - }, [openFlyout, ruleId]); - - return ( + return href ? ( {i18n.translate('xpack.securitySolution.flyout.preview.rule.viewDetailsLabel', { @@ -46,7 +35,7 @@ export const PreviewFooter = memo(({ ruleId }: { ruleId: string }) => { - ); + ) : null; }); PreviewFooter.displayName = 'PreviewFooter'; diff --git a/x-pack/plugins/security_solution/public/flyout/rule_details/right/index.test.tsx b/x-pack/plugins/security_solution/public/flyout/rule_details/right/index.test.tsx index 1ce755575450c..146da2be34346 100644 --- a/x-pack/plugins/security_solution/public/flyout/rule_details/right/index.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/rule_details/right/index.test.tsx @@ -10,7 +10,7 @@ import { render } from '@testing-library/react'; import { ThemeProvider } from 'styled-components'; import { getMockTheme } from '../../../common/lib/kibana/kibana_react.mock'; import { TestProviders } from '../../../common/mock'; -// import { TestProvider } from '@kbn/expandable-flyout/src/test/provider'; +import { useRuleDetailsLink } from '../../document_details/shared/hooks/use_rule_details_link'; import { RulePanel } from '.'; import { getStepsData } from '../../../detections/pages/detection_engine/rules/helpers'; import { useRuleDetails } from '../hooks/use_rule_details'; @@ -23,6 +23,8 @@ import type { RuleResponse } from '../../../../common/api/detection_engine'; import { BODY_TEST_ID, LOADING_TEST_ID } from './test_ids'; import { RULE_PREVIEW_FOOTER_TEST_ID } from '../preview/test_ids'; +jest.mock('../../document_details/shared/hooks/use_rule_details_link'); + const mockUseRuleDetails = useRuleDetails as jest.Mock; jest.mock('../hooks/use_rule_details'); @@ -89,6 +91,7 @@ describe('', () => { }); it('should render preview footer when isPreviewMode is true', () => { + (useRuleDetailsLink as jest.Mock).mockReturnValue('rule_details_link'); mockUseRuleDetails.mockReturnValue({ rule, loading: false, @@ -97,8 +100,6 @@ describe('', () => { mockGetStepsData.mockReturnValue({}); const { getByTestId } = renderRulePanel(true); - // await act(async () => { expect(getByTestId(RULE_PREVIEW_FOOTER_TEST_ID)).toBeInTheDocument(); - // }); }); }); diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts index 85cbbbe0eb7fd..0f93e4fceb10c 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel.ts @@ -89,8 +89,10 @@ ${JSON.stringify(cypressConfigFile, null, 2)} const specConfig = cypressConfigFile.e2e.specPattern; const specArg = argv.spec; const specPattern = specArg ?? specConfig; + const excludeSpecPattern = cypressConfigFile.e2e.excludeSpecPattern; log.info('Config spec pattern:', specConfig); + log.info('Exclude spec pattern:', excludeSpecPattern); log.info('Arguments spec pattern:', specArg); log.info('Resulting spec pattern:', specPattern); @@ -123,7 +125,14 @@ ${JSON.stringify(cypressConfigFile, null, 2)} const concreteFilePaths = isGrepReturnedFilePaths ? grepSpecPattern // use the returned concrete file paths - : globby.sync(specPattern); // convert the glob pattern to concrete file paths + : globby.sync( + specPattern, + excludeSpecPattern + ? { + ignore: excludeSpecPattern, + } + : undefined + ); // convert the glob pattern to concrete file paths let files = retrieveIntegrations(concreteFilePaths); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/alert_counts/alert_counts_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/alert_counts/alert_counts_tool.test.ts index 752f8e472a755..814a00853927f 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/alert_counts/alert_counts_tool.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/alert_counts/alert_counts_tool.test.ts @@ -29,13 +29,11 @@ describe('AlertCountsTool', () => { } as unknown as KibanaRequest; const isEnabledKnowledgeBase = true; const chain = {} as unknown as RetrievalQAChain; - const modelExists = true; const logger = loggerMock.create(); const rest = { isEnabledKnowledgeBase, chain, logger, - modelExists, }; beforeEach(() => { diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts index 5d8fb0b51739a..4d06751f57d7d 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts @@ -75,7 +75,6 @@ describe('AttackDiscoveryTool', () => { isEnabledKnowledgeBase: false, llm, logger, - modelExists: false, onNewReplacements: jest.fn(), size, }; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/esql/nl_to_esql_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/esql/nl_to_esql_tool.test.ts index f078bccb24a36..10b1fa21daefe 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/esql/nl_to_esql_tool.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/esql/nl_to_esql_tool.test.ts @@ -40,65 +40,18 @@ describe('NaturalLanguageESQLTool', () => { request, inference, connectorId, + isEnabledKnowledgeBase: true, }; describe('isSupported', () => { - it('returns false if isEnabledKnowledgeBase is false', () => { - const params = { - isEnabledKnowledgeBase: false, - modelExists: true, - ...rest, - }; - - expect(NL_TO_ESQL_TOOL.isSupported(params)).toBe(false); - }); - - it('returns false if modelExists is false (the ELSER model is not installed)', () => { - const params = { - isEnabledKnowledgeBase: true, - modelExists: false, // <-- ELSER model is not installed - ...rest, - }; - - expect(NL_TO_ESQL_TOOL.isSupported(params)).toBe(false); - }); - - it('returns true if isEnabledKnowledgeBase and modelExists are true', () => { - const params = { - isEnabledKnowledgeBase: true, - modelExists: true, - ...rest, - }; - - expect(NL_TO_ESQL_TOOL.isSupported(params)).toBe(true); + it('returns true if connectorId and inference have values', () => { + expect(NL_TO_ESQL_TOOL.isSupported(rest)).toBe(true); }); }); describe('getTool', () => { - it('returns null if isEnabledKnowledgeBase is false', () => { - const tool = NL_TO_ESQL_TOOL.getTool({ - isEnabledKnowledgeBase: false, - modelExists: true, - ...rest, - }); - - expect(tool).toBeNull(); - }); - - it('returns null if modelExists is false (the ELSER model is not installed)', () => { - const tool = NL_TO_ESQL_TOOL.getTool({ - isEnabledKnowledgeBase: true, - modelExists: false, // <-- ELSER model is not installed - ...rest, - }); - - expect(tool).toBeNull(); - }); - it('returns null if inference plugin is not provided', () => { const tool = NL_TO_ESQL_TOOL.getTool({ - isEnabledKnowledgeBase: true, - modelExists: true, ...rest, inference: undefined, }); @@ -108,8 +61,6 @@ describe('NaturalLanguageESQLTool', () => { it('returns null if connectorId is not provided', () => { const tool = NL_TO_ESQL_TOOL.getTool({ - isEnabledKnowledgeBase: true, - modelExists: true, ...rest, connectorId: undefined, }); @@ -117,10 +68,8 @@ describe('NaturalLanguageESQLTool', () => { expect(tool).toBeNull(); }); - it('should return a Tool instance if isEnabledKnowledgeBase and modelExists are true', () => { + it('should return a Tool instance when given required properties', () => { const tool = NL_TO_ESQL_TOOL.getTool({ - isEnabledKnowledgeBase: true, - modelExists: true, ...rest, }); @@ -129,8 +78,6 @@ describe('NaturalLanguageESQLTool', () => { it('should return a tool with the expected tags', () => { const tool = NL_TO_ESQL_TOOL.getTool({ - isEnabledKnowledgeBase: true, - modelExists: true, ...rest, }) as DynamicTool; @@ -139,8 +86,6 @@ describe('NaturalLanguageESQLTool', () => { it('should return tool with the expected description for OSS model', () => { const tool = NL_TO_ESQL_TOOL.getTool({ - isEnabledKnowledgeBase: true, - modelExists: true, isOssModel: true, ...rest, }) as DynamicTool; @@ -150,8 +95,6 @@ describe('NaturalLanguageESQLTool', () => { it('should return tool with the expected description for non-OSS model', () => { const tool = NL_TO_ESQL_TOOL.getTool({ - isEnabledKnowledgeBase: true, - modelExists: true, isOssModel: false, ...rest, }) as DynamicTool; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/esql/nl_to_esql_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/esql/nl_to_esql_tool.ts index 96b865efeaed4..1205fb03b0458 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/esql/nl_to_esql_tool.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/esql/nl_to_esql_tool.ts @@ -13,6 +13,7 @@ import { naturalLanguageToEsql } from '@kbn/inference-plugin/server'; import { APP_UI_ID } from '../../../../common'; import { getPromptSuffixForOssModel } from './common'; +// select only some properties of AssistantToolParams export type ESQLToolParams = AssistantToolParams; const TOOL_NAME = 'NaturalLanguageESQLTool'; @@ -32,8 +33,8 @@ export const NL_TO_ESQL_TOOL: AssistantTool = { ...toolDetails, sourceRegister: APP_UI_ID, isSupported: (params: ESQLToolParams): params is ESQLToolParams => { - const { inference, connectorId, isEnabledKnowledgeBase, modelExists } = params; - return isEnabledKnowledgeBase && modelExists && inference != null && connectorId != null; + const { inference, connectorId } = params; + return inference != null && connectorId != null; }, getTool(params: ESQLToolParams) { if (!this.isSupported(params)) return null; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_retrieval_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_retrieval_tool.ts index 7739de18857aa..cea2bdadf5970 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_retrieval_tool.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_retrieval_tool.ts @@ -25,8 +25,8 @@ export const KNOWLEDGE_BASE_RETRIEVAL_TOOL: AssistantTool = { ...toolDetails, sourceRegister: APP_UI_ID, isSupported: (params: AssistantToolParams): params is KnowledgeBaseRetrievalToolParams => { - const { kbDataClient, isEnabledKnowledgeBase, modelExists } = params; - return isEnabledKnowledgeBase && modelExists && kbDataClient != null; + const { kbDataClient, isEnabledKnowledgeBase } = params; + return isEnabledKnowledgeBase && kbDataClient != null; }, getTool(params: AssistantToolParams) { if (!this.isSupported(params)) return null; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_write_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_write_tool.ts index 9b46c625e115b..4069eeeef5b97 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_write_tool.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_write_tool.ts @@ -28,8 +28,8 @@ export const KNOWLEDGE_BASE_WRITE_TOOL: AssistantTool = { ...toolDetails, sourceRegister: APP_UI_ID, isSupported: (params: AssistantToolParams): params is KnowledgeBaseWriteToolParams => { - const { isEnabledKnowledgeBase, kbDataClient, modelExists } = params; - return isEnabledKnowledgeBase && modelExists && kbDataClient != null; + const { isEnabledKnowledgeBase, kbDataClient } = params; + return isEnabledKnowledgeBase && kbDataClient != null; }, getTool(params: AssistantToolParams) { if (!this.isSupported(params)) return null; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts index 2b134dfd86335..09bae1639f1b1 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts @@ -32,14 +32,12 @@ describe('OpenAndAcknowledgedAlertsTool', () => { } as unknown as KibanaRequest; const isEnabledKnowledgeBase = true; const chain = {} as unknown as RetrievalQAChain; - const modelExists = true; const logger = loggerMock.create(); const rest = { isEnabledKnowledgeBase, esClient, chain, logger, - modelExists, }; const anonymizationFields = [ diff --git a/x-pack/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts index 70e955dda8470..48e1619c2f00f 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/security_labs/security_labs_tool.ts @@ -22,8 +22,8 @@ export const SECURITY_LABS_KNOWLEDGE_BASE_TOOL: AssistantTool = { ...toolDetails, sourceRegister: APP_UI_ID, isSupported: (params: AssistantToolParams): params is AssistantToolParams => { - const { kbDataClient, isEnabledKnowledgeBase, modelExists } = params; - return isEnabledKnowledgeBase && modelExists && kbDataClient != null; + const { kbDataClient, isEnabledKnowledgeBase } = params; + return isEnabledKnowledgeBase && kbDataClient != null; }, getTool(params: AssistantToolParams) { if (!this.isSupported(params)) return null; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.mock.ts index c73203c2871ab..8f9c1a6a32357 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.mock.ts @@ -18,6 +18,7 @@ export const getPrebuiltRuleMock = (rewrites?: Partial): Preb language: 'kuery', rule_id: 'rule-1', version: 1, + author: [], ...rewrites, } as PrebuiltRuleAsset); @@ -51,6 +52,7 @@ export const getPrebuiltThreatMatchRuleMock = (): PrebuiltRuleAsset => ({ language: 'kuery', rule_id: 'rule-1', version: 1, + author: [], threat_query: '*:*', threat_index: ['list-index'], threat_mapping: [ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.patch_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.patch_rule.test.ts index e460581c02a1c..448df6b581a3b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.patch_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.patch_rule.test.ts @@ -277,6 +277,27 @@ describe('DetectionRulesClient.patchRule', () => { expect(rulesClient.create).not.toHaveBeenCalled(); }); + it('throws an error if rule has external rule source and non-customizable fields are changed', async () => { + // Mock the existing rule + const existingRule = { + ...getRulesSchemaMock(), + rule_source: { type: 'external', is_customized: true }, + }; + (getRuleByRuleId as jest.Mock).mockResolvedValueOnce(existingRule); + + // Mock the rule update + const rulePatch = getCreateRulesSchemaMock('query-rule-id'); + rulePatch.license = 'new license'; + + // Mock the rule returned after update; not used for this test directly but + // needed so that the patchRule method does not throw + rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); + + await expect(detectionRulesClient.patchRule({ rulePatch })).rejects.toThrow( + 'Cannot update "license" field for prebuilt rules' + ); + }); + describe('actions', () => { it("updates the rule's actions if provided", async () => { // Mock the existing rule diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.update_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.update_rule.test.ts index a660e5c5e8747..cbd0fb1fe3680 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.update_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.update_rule.test.ts @@ -498,5 +498,26 @@ describe('DetectionRulesClient.updateRule', () => { }) ); }); + + it('throws an error if rule has external rule source and non-customizable fields are changed', async () => { + // Mock the existing rule + const existingRule = { + ...getRulesSchemaMock(), + rule_source: { type: 'external', is_customized: true }, + }; + + (getRuleByRuleId as jest.Mock).mockResolvedValueOnce(existingRule); + + // Mock the rule update + const ruleUpdate = { ...getCreateRulesSchemaMock(), author: ['new user'] }; + + // Mock the rule returned after update; not used for this test directly but + // needed so that the patchRule method does not throw + rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); + + await expect(detectionRulesClient.updateRule({ ruleUpdate })).rejects.toThrow( + 'Cannot update "author" field for prebuilt rules' + ); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/patch_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/patch_rule.ts index 1218991bf388e..113576e8d02e2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/patch_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/patch_rule.ts @@ -16,6 +16,7 @@ import type { MlAuthz } from '../../../../../machine_learning/authz'; import type { IPrebuiltRuleAssetsClient } from '../../../../prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client'; import { applyRulePatch } from '../mergers/apply_rule_patch'; import { getIdError } from '../../../utils/utils'; +import { validateNonCustomizablePatchFields } from '../../../utils/validate'; import { convertAlertingRuleToRuleResponse } from '../converters/convert_alerting_rule_to_rule_response'; import { convertRuleResponseToAlertingRule } from '../converters/convert_rule_response_to_alerting_rule'; import { ClientError, toggleRuleEnabledOnUpdate, validateMlAuth } from '../utils'; @@ -51,6 +52,8 @@ export const patchRule = async ({ await validateMlAuth(mlAuthz, rulePatch.type ?? existingRule.type); + validateNonCustomizablePatchFields(rulePatch, existingRule); + const patchedRule = await applyRulePatch({ prebuiltRuleAssetClient, existingRule, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/update_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/update_rule.ts index cd84788026870..8fd7f7a89dcb7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/update_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/update_rule.ts @@ -11,6 +11,7 @@ import type { RuleResponse } from '../../../../../../../common/api/detection_eng import type { MlAuthz } from '../../../../../machine_learning/authz'; import { applyRuleUpdate } from '../mergers/apply_rule_update'; import { getIdError } from '../../../utils/utils'; +import { validateNonCustomizableUpdateFields } from '../../../utils/validate'; import { convertRuleResponseToAlertingRule } from '../converters/convert_rule_response_to_alerting_rule'; import { ClientError, toggleRuleEnabledOnUpdate, validateMlAuth } from '../utils'; @@ -50,6 +51,8 @@ export const updateRule = async ({ throw new ClientError(error.message, error.statusCode); } + validateNonCustomizableUpdateFields(ruleUpdate, existingRule); + const ruleWithUpdates = await applyRuleUpdate({ prebuiltRuleAssetClient, existingRule, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts index 3d07f935deb7b..5ff9d2d97f2b0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts @@ -15,6 +15,7 @@ import { RuleResponse, type RuleResponseAction, type RuleUpdateProps, + type RulePatchProps, } from '../../../../../common/api/detection_engine'; import { RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP, @@ -25,6 +26,7 @@ import { CustomHttpRequestError } from '../../../../utils/custom_http_request_er import { hasValidRuleType, type RuleAlertType, type RuleParams } from '../../rule_schema'; import { type BulkError, createBulkErrorObject } from '../../routes/utils'; import { internalRuleToAPIResponse } from '../logic/detection_rules_client/converters/internal_rule_to_api_response'; +import { ClientError } from '../logic/detection_rules_client/utils'; export const transformValidateBulkError = ( ruleId: string, @@ -117,3 +119,31 @@ function rulePayloadContainsResponseActions(rule: RuleCreateProps | RuleUpdatePr function ruleObjectContainsResponseActions(rule?: RuleAlertType) { return rule != null && 'params' in rule && 'responseActions' in rule?.params; } + +export const validateNonCustomizableUpdateFields = ( + ruleUpdate: RuleUpdateProps, + existingRule: RuleResponse +) => { + // We don't allow non-customizable fields to be changed for prebuilt rules + if (existingRule.rule_source && existingRule.rule_source.type === 'external') { + if (!isEqual(ruleUpdate.author, existingRule.author)) { + throw new ClientError(`Cannot update "author" field for prebuilt rules`, 400); + } else if (ruleUpdate.license !== existingRule.license) { + throw new ClientError(`Cannot update "license" field for prebuilt rules`, 400); + } + } +}; + +export const validateNonCustomizablePatchFields = ( + rulePatch: RulePatchProps, + existingRule: RuleResponse +) => { + // We don't allow non-customizable fields to be changed for prebuilt rules + if (existingRule.rule_source && existingRule.rule_source.type === 'external') { + if (rulePatch.author && !isEqual(rulePatch.author, existingRule.author)) { + throw new ClientError(`Cannot update "author" field for prebuilt rules`, 400); + } else if (rulePatch.license != null && rulePatch.license !== existingRule.license) { + throw new ClientError(`Cannot update "license" field for prebuilt rules`, 400); + } + } +}; diff --git a/x-pack/plugins/security_solution_serverless/server/common/services/usage_reporting_service.test.ts b/x-pack/plugins/security_solution_serverless/server/common/services/usage_reporting_service.test.ts new file mode 100644 index 0000000000000..e43df68cc200b --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/server/common/services/usage_reporting_service.test.ts @@ -0,0 +1,179 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import fetch from 'node-fetch'; +import https from 'https'; +import { merge } from 'lodash'; + +import { KBN_CERT_PATH, KBN_KEY_PATH, CA_CERT_PATH } from '@kbn/dev-utils'; + +import type { UsageApiConfigSchema } from '../../config'; +import type { UsageRecord } from '../../types'; + +import { UsageReportingService } from './usage_reporting_service'; +import { USAGE_REPORTING_ENDPOINT, USAGE_SERVICE_USAGE_URL } from '../../constants'; + +jest.mock('node-fetch'); +const { Response } = jest.requireActual('node-fetch'); + +describe('UsageReportingService', () => { + let usageApiConfig: UsageApiConfigSchema; + let service: UsageReportingService; + + function generateUsageApiConfig(overrides?: Partial): UsageApiConfigSchema { + const DEFAULT_USAGE_API_CONFIG = { enabled: false }; + usageApiConfig = merge(DEFAULT_USAGE_API_CONFIG, overrides); + + return usageApiConfig; + } + + function setupService( + usageApi: UsageApiConfigSchema = generateUsageApiConfig() + ): UsageReportingService { + service = new UsageReportingService(usageApi); + return service; + } + + function generateUsageRecord(overrides?: Partial): UsageRecord { + const date = new Date().toISOString(); + const DEFAULT_USAGE_RECORD = { + id: `usage-record-id-${date}`, + usage_timestamp: date, + creation_timestamp: date, + usage: {}, + source: {}, + } as UsageRecord; + return merge(DEFAULT_USAGE_RECORD, overrides); + } + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('usageApi configs not provided', () => { + beforeEach(() => { + setupService(); + }); + + it('should still work if usageApi.url is not provided', async () => { + const usageRecord = generateUsageRecord(); + const records: UsageRecord[] = [usageRecord]; + const mockResponse = new Response(null, { status: 200 }); + (fetch as jest.MockedFunction).mockResolvedValueOnce(mockResponse); + + const response = await service.reportUsage(records); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith(USAGE_SERVICE_USAGE_URL, { + method: 'post', + body: JSON.stringify(records), + headers: { 'Content-Type': 'application/json' }, + agent: expect.any(https.Agent), + }); + expect(response).toBe(mockResponse); + }); + + it('should use an agent with rejectUnauthorized false if config.enabled is false', async () => { + const usageRecord = generateUsageRecord(); + const records: UsageRecord[] = [usageRecord]; + const mockResponse = new Response(null, { status: 200 }); + (fetch as jest.MockedFunction).mockResolvedValueOnce(mockResponse); + + const response = await service.reportUsage(records); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith(USAGE_SERVICE_USAGE_URL, { + method: 'post', + body: JSON.stringify(records), + headers: { 'Content-Type': 'application/json' }, + agent: expect.objectContaining({ + options: expect.objectContaining({ rejectUnauthorized: false }), + }), + }); + expect(response).toBe(mockResponse); + }); + + it('should not set agent if the URL is not https', async () => { + const url = 'http://usage-api.example'; + setupService(generateUsageApiConfig({ url })); + const usageRecord = generateUsageRecord(); + const records: UsageRecord[] = [usageRecord]; + const mockResponse = new Response(null, { status: 200 }); + (fetch as jest.MockedFunction).mockResolvedValue(mockResponse); + + const response = await service.reportUsage(records); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith(`${url}${USAGE_REPORTING_ENDPOINT}`, { + method: 'post', + body: JSON.stringify(records), + headers: { 'Content-Type': 'application/json' }, + }); + expect(response).toBe(mockResponse); + }); + }); + + describe('usageApi configs provided', () => { + const DEFAULT_CONFIG = { + enabled: true, + url: 'https://usage-api.example', + tls: { + certificate: KBN_CERT_PATH, + key: KBN_KEY_PATH, + ca: CA_CERT_PATH, + }, + }; + + beforeEach(() => { + setupService(generateUsageApiConfig(DEFAULT_CONFIG)); + }); + + it('should use usageApi.url if provided', async () => { + const usageRecord = generateUsageRecord(); + const records: UsageRecord[] = [usageRecord]; + const mockResponse = new Response(null, { status: 200 }); + (fetch as jest.MockedFunction).mockResolvedValueOnce(mockResponse); + + const response = await service.reportUsage(records); + const url = `${DEFAULT_CONFIG.url}${USAGE_REPORTING_ENDPOINT}`; + + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith(url, { + method: 'post', + body: JSON.stringify(records), + headers: { 'Content-Type': 'application/json' }, + agent: expect.any(https.Agent), + }); + expect(response).toBe(mockResponse); + }); + + it('should use an agent with TLS configuration if config.enabled is true', async () => { + const usageRecord = generateUsageRecord(); + const records: UsageRecord[] = [usageRecord]; + const mockResponse = new Response(null, { status: 200 }); + (fetch as jest.MockedFunction).mockResolvedValueOnce(mockResponse); + + const response = await service.reportUsage(records); + const url = `${DEFAULT_CONFIG.url}${USAGE_REPORTING_ENDPOINT}`; + + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith(url, { + method: 'post', + body: JSON.stringify(records), + headers: { 'Content-Type': 'application/json' }, + agent: expect.objectContaining({ + options: expect.objectContaining({ + cert: expect.any(String), + key: expect.any(String), + ca: expect.arrayContaining([expect.any(String)]), + }), + }), + }); + expect(response).toBe(mockResponse); + }); + }); +}); diff --git a/x-pack/plugins/security_solution_serverless/server/common/services/usage_reporting_service.ts b/x-pack/plugins/security_solution_serverless/server/common/services/usage_reporting_service.ts index 0e47b982e692e..ee402872ef33a 100644 --- a/x-pack/plugins/security_solution_serverless/server/common/services/usage_reporting_service.ts +++ b/x-pack/plugins/security_solution_serverless/server/common/services/usage_reporting_service.ts @@ -5,29 +5,77 @@ * 2.0. */ -import type { Response } from 'node-fetch'; +import type { RequestInit, Response } from 'node-fetch'; + import fetch from 'node-fetch'; import https from 'https'; -import { USAGE_SERVICE_USAGE_URL } from '../../constants'; +import { SslConfig, sslSchema } from '@kbn/server-http-tools'; + import type { UsageRecord } from '../../types'; +import type { UsageApiConfigSchema, TlsConfigSchema } from '../../config'; + +import { USAGE_REPORTING_ENDPOINT, USAGE_SERVICE_USAGE_URL } from '../../constants'; -// TODO remove once we have the CA available -const agent = new https.Agent({ rejectUnauthorized: false }); export class UsageReportingService { - public async reportUsage( - records: UsageRecord[], - url = USAGE_SERVICE_USAGE_URL - ): Promise { - const isHttps = url.includes('https'); + private agent: https.Agent | undefined; - return fetch(url, { + constructor(private readonly config: UsageApiConfigSchema) {} + + public async reportUsage(records: UsageRecord[]): Promise { + const reqArgs: RequestInit = { method: 'post', body: JSON.stringify(records), headers: { 'Content-Type': 'application/json' }, - agent: isHttps ? agent : undefined, // Conditionally add agent if URL is HTTPS for supporting integration tests. + }; + if (this.usageApiUrl.includes('https')) { + reqArgs.agent = this.httpAgent; + } + return fetch(this.usageApiUrl, reqArgs); + } + + private get tlsConfigs(): NonNullable { + if (!this.config.tls) { + throw new Error('UsageReportingService: usageApi.tls configs not provided'); + } + + return this.config.tls; + } + + private get usageApiUrl(): string { + if (!this.config.url) { + return USAGE_SERVICE_USAGE_URL; + } + + return `${this.config.url}${USAGE_REPORTING_ENDPOINT}`; + } + + private get httpAgent(): https.Agent { + if (this.agent) { + return this.agent; + } + + if (!this.config.enabled) { + this.agent = new https.Agent({ rejectUnauthorized: false }); + return this.agent; + } + + const tlsConfig = new SslConfig( + sslSchema.validate({ + enabled: true, + certificate: this.tlsConfigs.certificate, + key: this.tlsConfigs.key, + certificateAuthorities: this.tlsConfigs.ca, + }) + ); + + this.agent = new https.Agent({ + rejectUnauthorized: tlsConfig.rejectUnauthorized, + cert: tlsConfig.certificate, + key: tlsConfig.key, + ca: tlsConfig.certificateAuthorities, }); + + return this.agent; } } - -export const usageReportingService = new UsageReportingService(); diff --git a/x-pack/plugins/security_solution_serverless/server/config.ts b/x-pack/plugins/security_solution_serverless/server/config.ts index 96e743a59b425..d4bafd9b9ddb9 100644 --- a/x-pack/plugins/security_solution_serverless/server/config.ts +++ b/x-pack/plugins/security_solution_serverless/server/config.ts @@ -16,19 +16,19 @@ import type { ExperimentalFeatures } from '../common/experimental_features'; import { productTypes } from '../common/config'; import { parseExperimentalConfigValue } from '../common/experimental_features'; -const usageApiConfig = schema.maybe( - schema.object({ - enabled: schema.maybe(schema.boolean()), - url: schema.string(), - tls: schema.maybe( - schema.object({ - certificate: schema.string(), - key: schema.string(), - ca: schema.string(), - }) - ), - }) -); +const tlsConfig = schema.object({ + certificate: schema.string(), + key: schema.string(), + ca: schema.string(), +}); +export type TlsConfigSchema = TypeOf; + +const usageApiConfig = schema.object({ + enabled: schema.boolean({ defaultValue: false }), + url: schema.maybe(schema.string()), + tls: schema.maybe(tlsConfig), +}); +export type UsageApiConfigSchema = TypeOf; export const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: false }), diff --git a/x-pack/plugins/security_solution_serverless/server/constants.ts b/x-pack/plugins/security_solution_serverless/server/constants.ts index f4fcad6b760c6..411a7209682de 100644 --- a/x-pack/plugins/security_solution_serverless/server/constants.ts +++ b/x-pack/plugins/security_solution_serverless/server/constants.ts @@ -9,4 +9,5 @@ const namespace = 'elastic-system'; const USAGE_SERVICE_BASE_API_URL = `https://usage-api.${namespace}/api`; const USAGE_SERVICE_BASE_API_URL_V1 = `${USAGE_SERVICE_BASE_API_URL}/v1`; export const USAGE_SERVICE_USAGE_URL = `${USAGE_SERVICE_BASE_API_URL_V1}/usage`; +export const USAGE_REPORTING_ENDPOINT = '/api/v1/usage'; export const METERING_SERVICE_BATCH_SIZE = 1000; diff --git a/x-pack/plugins/security_solution_serverless/server/plugin.ts b/x-pack/plugins/security_solution_serverless/server/plugin.ts index 7161c5b684505..c249e48ca13a0 100644 --- a/x-pack/plugins/security_solution_serverless/server/plugin.ts +++ b/x-pack/plugins/security_solution_serverless/server/plugin.ts @@ -34,6 +34,7 @@ import { } from './endpoint/services'; import { NLPCleanupTask } from './task_manager/nlp_cleanup_task/nlp_cleanup_task'; import { telemetryEvents } from './telemetry/event_based_telemetry'; +import { UsageReportingService } from './common/services/usage_reporting_service'; export class SecuritySolutionServerlessPlugin implements @@ -49,11 +50,14 @@ export class SecuritySolutionServerlessPlugin private endpointUsageReportingTask: SecurityUsageReportingTask | undefined; private nlpCleanupTask: NLPCleanupTask | undefined; private readonly logger: Logger; + private readonly usageReportingService: UsageReportingService; constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get(); this.logger = this.initializerContext.logger.get(); + this.usageReportingService = new UsageReportingService(this.config.usageApi); + const productTypesStr = JSON.stringify(this.config.productTypes, null, 2); this.logger.info(`Security Solution running with product types:\n${productTypesStr}`); } @@ -83,6 +87,7 @@ export class SecuritySolutionServerlessPlugin taskTitle: cloudSecurityMetringTaskProperties.taskTitle, version: cloudSecurityMetringTaskProperties.version, meteringCallback: cloudSecurityMetringTaskProperties.meteringCallback, + usageReportingService: this.usageReportingService, }); this.endpointUsageReportingTask = new SecurityUsageReportingTask({ @@ -95,6 +100,7 @@ export class SecuritySolutionServerlessPlugin meteringCallback: endpointMeteringService.getUsageRecords, taskManager: pluginsSetup.taskManager, cloudSetup: pluginsSetup.cloud, + usageReportingService: this.usageReportingService, }); this.nlpCleanupTask = new NLPCleanupTask({ diff --git a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts index 66307e8f8a693..01c38ed6eed31 100644 --- a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts +++ b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts @@ -7,28 +7,26 @@ import { assign } from 'lodash'; +import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import type { CoreSetup, ElasticsearchClient } from '@kbn/core/server'; import type { TaskManagerSetupContract, ConcreteTaskInstance, } from '@kbn/task-manager-plugin/server'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; + import { TaskStatus } from '@kbn/task-manager-plugin/server'; import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import { coreMock } from '@kbn/core/server/mocks'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; -import { ProductLine, ProductTier } from '../../common/product'; - -import { usageReportingService } from '../common/services'; import type { ServerlessSecurityConfig } from '../config'; import type { SecurityUsageReportingTaskSetupContract, UsageRecord } from '../types'; +import { ProductLine, ProductTier } from '../../common/product'; import { SecurityUsageReportingTask } from './usage_reporting_task'; import { endpointMeteringService } from '../endpoint/services'; -import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; -import { USAGE_SERVICE_USAGE_URL } from '../constants'; describe('SecurityUsageReportingTask', () => { const TITLE = 'test-task-title'; @@ -45,7 +43,7 @@ describe('SecurityUsageReportingTask', () => { let mockEsClient: jest.Mocked; let mockCore: CoreSetup; let mockTaskManagerSetup: jest.Mocked; - let reportUsageSpy: jest.SpyInstance; + let reportUsageMock: jest.Mock; let meteringCallbackMock: jest.Mock; let taskArgs: SecurityUsageReportingTaskSetupContract; let usageRecord: UsageRecord; @@ -118,11 +116,24 @@ describe('SecurityUsageReportingTask', () => { taskTitle: TITLE, version: VERSION, meteringCallback: meteringCallbackMock, + usageReportingService: { + reportUsage: reportUsageMock, + }, }, overrides ); } + const USAGE_API_CONFIG = { + enabled: true, + url: 'https://usage-api-url', + tls: { + certificate: '', + key: '', + ca: '', + }, + }; + async function runTask(taskInstance = buildMockTaskInstance(), callNum: number = 0) { const mockTaskManagerStart = tmStartMock(); await mockTask.start({ taskManager: mockTaskManagerStart, interval: '5m' }); @@ -138,7 +149,7 @@ describe('SecurityUsageReportingTask', () => { .asInternalUser as jest.Mocked; mockTaskManagerSetup = tmSetupMock(); usageRecord = buildUsageRecord(); - reportUsageSpy = jest.spyOn(usageReportingService, 'reportUsage'); + reportUsageMock = jest.fn(); } describe('meteringCallback integration', () => { @@ -150,7 +161,7 @@ describe('SecurityUsageReportingTask', () => { productTypes: [ { product_line: ProductLine.endpoint, product_tier: ProductTier.complete }, ], - usageApi: { url: USAGE_SERVICE_USAGE_URL }, + usageApi: USAGE_API_CONFIG, } as ServerlessSecurityConfig, }); mockTask = new SecurityUsageReportingTask(taskArgs); @@ -199,9 +210,9 @@ describe('SecurityUsageReportingTask', () => { await runTasksUntilNoRunAt(); - expect(reportUsageSpy).toHaveBeenCalledTimes(3); + expect(reportUsageMock).toHaveBeenCalledTimes(3); batches.forEach((batch, i) => { - expect(reportUsageSpy).toHaveBeenNthCalledWith( + expect(reportUsageMock).toHaveBeenNthCalledWith( i + 1, expect.arrayContaining( batch.map(({ _source }) => @@ -209,8 +220,7 @@ describe('SecurityUsageReportingTask', () => { id: `endpoint-${_source.agent.id}-2021-09-01T00:00:00.000Z`, }) ) - ), - USAGE_SERVICE_USAGE_URL + ) ); }); }); @@ -227,7 +237,7 @@ describe('SecurityUsageReportingTask', () => { }); taskArgs = buildTaskArgs({ config: { - usageApi: { url: USAGE_SERVICE_USAGE_URL }, + usageApi: USAGE_API_CONFIG, } as ServerlessSecurityConfig, }); mockTask = new SecurityUsageReportingTask(taskArgs); @@ -273,7 +283,7 @@ describe('SecurityUsageReportingTask', () => { it('should report metering records', async () => { await runTask(); - expect(reportUsageSpy).toHaveBeenCalledWith( + expect(reportUsageMock).toHaveBeenCalledWith( expect.arrayContaining([ expect.objectContaining({ creation_timestamp: usageRecord.creation_timestamp, @@ -286,8 +296,7 @@ describe('SecurityUsageReportingTask', () => { usage: { period_seconds: 3600, quantity: 1, type: USAGE_TYPE }, usage_timestamp: usageRecord.usage_timestamp, }), - ]), - USAGE_SERVICE_USAGE_URL + ]) ); }); @@ -296,12 +305,12 @@ describe('SecurityUsageReportingTask', () => { expect(result).toEqual(getDeleteTaskRunResult()); - expect(reportUsageSpy).not.toHaveBeenCalled(); + expect(reportUsageMock).not.toHaveBeenCalled(); expect(meteringCallbackMock).not.toHaveBeenCalled(); }); describe('lastSuccessfulReport', () => { it('should set lastSuccessfulReport correctly if report success', async () => { - reportUsageSpy.mockResolvedValueOnce({ status: 201 }); + reportUsageMock.mockResolvedValueOnce({ status: 201 }); const taskInstance = buildMockTaskInstance(); const task = await runTask(taskInstance); const newLastSuccessfulReport = task?.state.lastSuccessfulReport; @@ -320,7 +329,7 @@ describe('SecurityUsageReportingTask', () => { describe('and response is NOT 201', () => { beforeEach(() => { - reportUsageSpy.mockResolvedValueOnce({ status: 500 }); + reportUsageMock.mockResolvedValueOnce({ status: 500 }); }); it('should set lastSuccessfulReport correctly', async () => { diff --git a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts index 83ef25a849f2d..6eb682a84d474 100644 --- a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts +++ b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts @@ -8,10 +8,10 @@ import type { Response } from 'node-fetch'; import type { CoreSetup, Logger } from '@kbn/core/server'; import type { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server'; -import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; -import { usageReportingService } from '../common/services'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; + import type { MeteringCallback, SecurityUsageReportingTaskStartContract, @@ -19,6 +19,7 @@ import type { UsageRecord, } from '../types'; import type { ServerlessSecurityConfig } from '../config'; +import type { UsageReportingService } from '../common/services/usage_reporting_service'; import { stateSchemaByVersion, emptyState } from './task_state'; @@ -34,6 +35,7 @@ export class SecurityUsageReportingTask { private readonly version: string; private readonly logger: Logger; private readonly config: ServerlessSecurityConfig; + private readonly usageReportingService: UsageReportingService; constructor(setupContract: SecurityUsageReportingTaskSetupContract) { const { @@ -46,6 +48,7 @@ export class SecurityUsageReportingTask { taskTitle, version, meteringCallback, + usageReportingService, } = setupContract; this.cloudSetup = cloudSetup; @@ -53,6 +56,7 @@ export class SecurityUsageReportingTask { this.version = version; this.logger = logFactory.get(this.taskId); this.config = config; + this.usageReportingService = usageReportingService; try { taskManager.registerTaskDefinitions({ @@ -163,10 +167,7 @@ export class SecurityUsageReportingTask { try { this.logger.debug(`Sending ${usageRecords.length} usage records to the API`); - usageReportResponse = await usageReportingService.reportUsage( - usageRecords, - this.config.usageApi?.url - ); + usageReportResponse = await this.usageReportingService.reportUsage(usageRecords); if (!usageReportResponse.ok) { const errorResponse = await usageReportResponse.json(); diff --git a/x-pack/plugins/security_solution_serverless/server/types.ts b/x-pack/plugins/security_solution_serverless/server/types.ts index 4f3a7bf3c3db0..a838c410793c3 100644 --- a/x-pack/plugins/security_solution_serverless/server/types.ts +++ b/x-pack/plugins/security_solution_serverless/server/types.ts @@ -25,6 +25,7 @@ import type { IntegrationAssistantPluginSetup } from '@kbn/integration-assistant import type { ProductTier } from '../common/product'; import type { ServerlessSecurityConfig } from './config'; +import type { UsageReportingService } from './common/services/usage_reporting_service'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SecuritySolutionServerlessPluginSetup {} @@ -86,6 +87,7 @@ export interface SecurityUsageReportingTaskSetupContract { taskTitle: string; version: string; meteringCallback: MeteringCallback; + usageReportingService: UsageReportingService; } export interface SecurityUsageReportingTaskStartContract { diff --git a/x-pack/plugins/security_solution_serverless/tsconfig.json b/x-pack/plugins/security_solution_serverless/tsconfig.json index 55a4882655dc7..cb0518fc4dcd5 100644 --- a/x-pack/plugins/security_solution_serverless/tsconfig.json +++ b/x-pack/plugins/security_solution_serverless/tsconfig.json @@ -19,6 +19,7 @@ "@kbn/security-plugin", "@kbn/security-solution-ess", "@kbn/security-solution-plugin", + "@kbn/server-http-tools", "@kbn/serverless", "@kbn/security-solution-navigation", "@kbn/security-solution-upselling", @@ -46,5 +47,6 @@ "@kbn/logging", "@kbn/integration-assistant-plugin", "@kbn/cloud-security-posture-common", + "@kbn/dev-utils" ] } diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.test.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.test.tsx index 6686d56173de4..e1d17b79e612d 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.test.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.test.tsx @@ -6,7 +6,6 @@ */ import React from 'react'; -import 'brace'; import { of, Subject } from 'rxjs'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.test.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.test.tsx index 64d075b7ba723..568a8cf226ae2 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.test.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.test.tsx @@ -6,7 +6,6 @@ */ import type { DataView } from '@kbn/data-views-plugin/public'; import { mountWithIntl } from '@kbn/test-jest-helpers'; -import 'brace'; import React, { useState } from 'react'; import { docLinksServiceMock } from '@kbn/core/public/mocks'; import { httpServiceMock } from '@kbn/core/public/mocks'; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.tsx index 196d138c68964..2f0c46a5e34c5 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/expression.tsx @@ -7,7 +7,6 @@ import React, { memo, PropsWithChildren, useCallback } from 'react'; import deepEqual from 'fast-deep-equal'; -import 'brace/theme/github'; import { EuiCallOut, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index bce366673a3f0..0b6810adcff68 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -9376,6 +9376,94 @@ "xpack.actions.serverSideErrors.unavailableLicenseInformationErrorMessage": "Les actions sont indisponibles - les informations de licence ne sont pas disponibles actuellement.", "xpack.actions.subActionsFramework.urlValidationError": "Erreur lors de la validation de l'URL : {message}", "xpack.actions.urlAllowedHostsConfigurationError": "Le {field} cible \"{value}\" n'est pas ajouté à la configuration Kibana xpack.actions.allowedHosts", + "xpack.aiAssistant.askAssistantButton.buttonLabel": "Demander à l'assistant", + "xpack.aiAssistant.askAssistantButton.popoverContent": "Obtenez des informations relatives à vos données grâce à l'assistant d'Elastic", + "xpack.aiAssistant.assistantSetup.title": "Bienvenue sur l'assistant d'intelligence artificielle d'Elastic", + "xpack.aiAssistant.chatActionsMenu.euiButtonIcon.menuLabel": "Menu", + "xpack.aiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel": "Plus d'actions", + "xpack.aiAssistant.chatCollapsedItems.hideEvents": "Masquer {count} événements", + "xpack.aiAssistant.chatCollapsedItems.showEvents": "Montrer {count} événements", + "xpack.aiAssistant.chatCollapsedItems.toggleButtonLabel": "Afficher/masquer les éléments", + "xpack.aiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel": "Développer la liste des conversations", + "xpack.aiAssistant.chatFlyout.euiButtonIcon.newChatLabel": "Nouveau chat", + "xpack.aiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel": "Réduire la liste des conversations", + "xpack.aiAssistant.chatFlyout.euiToolTip.expandConversationListLabel": "Développer la liste des conversations", + "xpack.aiAssistant.chatFlyout.euiToolTip.newChatLabel": "Nouveau chat", + "xpack.aiAssistant.chatHeader.actions.connector": "Connecteur", + "xpack.aiAssistant.chatHeader.actions.copyConversation": "Copier la conversation", + "xpack.aiAssistant.chatHeader.actions.knowledgeBase": "Gérer la base de connaissances", + "xpack.aiAssistant.chatHeader.actions.settings": "Réglages de l'assistant d'IA", + "xpack.aiAssistant.chatHeader.actions.title": "Actions", + "xpack.aiAssistant.chatHeader.editConversationInput": "Modifier la conversation", + "xpack.aiAssistant.chatHeader.euiButtonIcon.navigateToConversationsLabel": "Accéder aux conversations", + "xpack.aiAssistant.chatHeader.euiButtonIcon.toggleFlyoutModeLabel": "Afficher / Masquer le mode menu volant", + "xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock": "Ancrer le chat", + "xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock": "Désancrer le chat", + "xpack.aiAssistant.chatHeader.euiToolTip.navigateToConversationsLabel": "Accéder aux conversations", + "xpack.aiAssistant.chatPromptEditor.codeEditor.payloadEditorLabel": "payloadEditor", + "xpack.aiAssistant.chatPromptEditor.euiButtonIcon.submitLabel": "Envoyer", + "xpack.aiAssistant.chatTimeline.actions.copyMessage": "Copier le message", + "xpack.aiAssistant.chatTimeline.actions.copyMessageSuccessful": "Message copié", + "xpack.aiAssistant.chatTimeline.actions.editPrompt": "Modifier l'invite", + "xpack.aiAssistant.chatTimeline.actions.inspectPrompt": "Inspecter l'invite", + "xpack.aiAssistant.chatTimeline.messages.elasticAssistant.label": "Assistant d'Elastic", + "xpack.aiAssistant.chatTimeline.messages.system.label": "Système", + "xpack.aiAssistant.chatTimeline.messages.user.label": "Vous", + "xpack.aiAssistant.checkingKbAvailability": "Vérification de la disponibilité de la base de connaissances", + "xpack.aiAssistant.conversationStartTitle": "a démarré une conversation", + "xpack.aiAssistant.couldNotFindConversationContent": "Impossible de trouver une conversation avec l'ID {conversationId}. Assurez-vous que la conversation existe et que vous y avez accès.", + "xpack.aiAssistant.couldNotFindConversationTitle": "Conversation introuvable", + "xpack.aiAssistant.disclaimer.disclaimerLabel": "Ce chat est soutenu par une intégration avec votre fournisseur LLM. Il arrive que les grands modèles de langage (LLM) présentent comme correctes des informations incorrectes. Elastic prend en charge la configuration ainsi que la connexion au fournisseur LLM et à votre base de connaissances, mais n'est pas responsable des réponses fournies par le LLM.", + "xpack.aiAssistant.emptyConversationTitle": "Nouvelle conversation", + "xpack.aiAssistant.errorSettingUpKnowledgeBase": "Impossible de configurer la base de connaissances", + "xpack.aiAssistant.errorUpdatingConversation": "Impossible de mettre à jour la conversation", + "xpack.aiAssistant.executedFunctionFailureEvent": "impossible d'exécuter la fonction {functionName}", + "xpack.aiAssistant.failedToGetStatus": "Échec de l'obtention du statut du modèle.", + "xpack.aiAssistant.failedToSetupKnowledgeBase": "Échec de la configuration de la base de connaissances.", + "xpack.aiAssistant.flyout.confirmDeleteButtonText": "Supprimer la conversation", + "xpack.aiAssistant.flyout.confirmDeleteConversationContent": "Cette action ne peut pas être annulée.", + "xpack.aiAssistant.flyout.confirmDeleteConversationTitle": "Supprimer cette conversation ?", + "xpack.aiAssistant.flyout.failedToDeleteConversation": "Impossible de supprimer la conversation", + "xpack.aiAssistant.functionListPopover.euiButtonIcon.selectAFunctionLabel": "Sélectionner la fonction", + "xpack.aiAssistant.functionListPopover.euiToolTip.clearFunction": "Effacer la fonction", + "xpack.aiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel": "Sélectionner une fonction", + "xpack.aiAssistant.hideExpandConversationButton.hide": "Masquer les chats", + "xpack.aiAssistant.hideExpandConversationButton.show": "Afficher les chats", + "xpack.aiAssistant.incorrectLicense.body": "Une licence d'entreprise est requise pour utiliser l'assistant d'intelligence artificielle d'Elastic.", + "xpack.aiAssistant.incorrectLicense.manageLicense": "Gérer la licence", + "xpack.aiAssistant.incorrectLicense.subscriptionPlansButton": "Plans d'abonnement", + "xpack.aiAssistant.incorrectLicense.title": "Mettez votre licence à niveau", + "xpack.aiAssistant.initialSetupPanel.setupConnector.buttonLabel": "Configurer un connecteur GenAI", + "xpack.aiAssistant.initialSetupPanel.setupConnector.description2": "Commencez à travailler avec l'assistant AI Elastic en configurant un connecteur pour votre fournisseur d'IA. Le modèle doit prendre en charge les appels de fonction. Lorsque vous utilisez OpenAI ou Azure, nous vous recommandons d'utiliser GPT4.", + "xpack.aiAssistant.installingKb": "Configuration de la base de connaissances", + "xpack.aiAssistant.newChatButton": "Nouveau chat", + "xpack.aiAssistant.poweredByModel": "Alimenté par {model}", + "xpack.aiAssistant.prompt.functionList.filter": "Filtre", + "xpack.aiAssistant.prompt.functionList.functionList": "Liste de fonctions", + "xpack.aiAssistant.prompt.placeholder": "Envoyer un message à l'assistant", + "xpack.aiAssistant.promptEditorNaturalLanguage.euiSelectable.selectAnOptionLabel": "Sélectionner une option", + "xpack.aiAssistant.settingsPage.goToConnectorsButtonLabel": "Gérer les connecteurs", + "xpack.aiAssistant.setupKb": "Améliorez votre expérience en configurant la base de connaissances.", + "xpack.aiAssistant.simulatedFunctionCallingCalloutLabel": "L'appel de fonctions simulées est activé. Vous risquez de voir les performances se dégrader.", + "xpack.aiAssistant.suggestedFunctionEvent": "a demandé la fonction {functionName}", + "xpack.aiAssistant.technicalPreviewBadgeDescription": "GTP4 est nécessaire pour bénéficier d'une meilleure expérience avec les appels de fonctions (par exemple lors de la réalisation d'analyse de la cause d'un problème, de la visualisation de données et autres). GPT3.5 peut fonctionner pour certains des workflows les plus simples comme les explications d'erreurs ou pour bénéficier d'une expérience comparable à ChatGPT au sein de Kibana à partir du moment où les appels de fonctions ne sont pas fréquents.", + "xpack.aiAssistant.userExecutedFunctionEvent": "a exécuté la fonction {functionName}", + "xpack.aiAssistant.userSuggestedFunctionEvent": "a demandé la fonction {functionName}", + "xpack.aiAssistant.welcomeMessage.div.checkTrainedModelsToLabel": " {retryInstallingLink} ou vérifiez {trainedModelsLink} pour vous assurer que {modelName} est déployé et en cours d'exécution.", + "xpack.aiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel": "Configuration de la base de connaissances", + "xpack.aiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel": "Inspecter les problèmes", + "xpack.aiAssistant.welcomeMessage.issuesDescriptionListTitleLabel": "Problèmes", + "xpack.aiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel": "La base de connaissances a été installée avec succès", + "xpack.aiAssistant.welcomeMessage.modelIsNotDeployedLabel": "Le modèle {modelName} n'est pas déployé", + "xpack.aiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel": "L'état d'allocation de {modelName} est {allocationState}", + "xpack.aiAssistant.welcomeMessage.modelIsNotStartedLabel": "L'état de déploiement de {modelName} est {deploymentState}", + "xpack.aiAssistant.welcomeMessage.retryButtonLabel": "Installer la base de connaissances", + "xpack.aiAssistant.welcomeMessage.trainedModelsLinkLabel": "Modèles entraînés", + "xpack.aiAssistant.welcomeMessage.weAreSettingUpTextLabel": "Nous configurons votre base de connaissances. Cette opération peut prendre quelques minutes. Vous pouvez continuer à utiliser l'Assistant lors de ce processus.", + "xpack.aiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel": "Impossible de charger les connecteurs", + "xpack.aiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel": "Vous n'avez pas les autorisations requises pour charger les connecteurs", + "xpack.aiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel": "Votre base de connaissances n'a pas été configurée.", + "xpack.aiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel": "Réessayer l'installation", "xpack.aiops.actions.openChangePointInMlAppName": "Ouvrir dans AIOps Labs", "xpack.aiops.analysis.columnSelectorAriaLabel": "Filtrer les colonnes", "xpack.aiops.analysis.columnSelectorNotEnoughColumnsSelected": "Au moins une colonne doit être sélectionnée.", @@ -32542,92 +32630,27 @@ "xpack.observabilityAiAssistant.app.starterPrompts.whatAreSlos.prompt": "Que signifie \"SLO\" ?", "xpack.observabilityAiAssistant.app.starterPrompts.whatAreSlos.title": "SLO", "xpack.observabilityAiAssistant.appTitle": "Assistant d'intelligence artificielle d'Observability", - "xpack.observabilityAiAssistant.askAssistantButton.buttonLabel": "Demander à l'assistant", - "xpack.observabilityAiAssistant.askAssistantButton.popoverContent": "Obtenez des informations relatives à vos données grâce à l'assistant d'Elastic", - "xpack.observabilityAiAssistant.askAssistantButton.popoverTitle": "Assistant d'IA pour Observability", - "xpack.observabilityAiAssistant.assistantSetup.title": "Bienvenue sur l'assistant d'intelligence artificielle d'Elastic", "xpack.observabilityAiAssistant.changesList.dotImpactHigh": "Élevé", "xpack.observabilityAiAssistant.changesList.dotImpactLow": "Bas", "xpack.observabilityAiAssistant.changesList.dotImpactMedium": "Moyenne", "xpack.observabilityAiAssistant.changesList.labelColumnTitle": "Étiquette", "xpack.observabilityAiAssistant.changesList.noChangesDetected": "Aucun changement détecté", "xpack.observabilityAiAssistant.changesList.trendColumnTitle": "Tendance", - "xpack.observabilityAiAssistant.chatActionsMenu.euiButtonIcon.menuLabel": "Menu", - "xpack.observabilityAiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel": "Plus d'actions", - "xpack.observabilityAiAssistant.chatCollapsedItems.hideEvents": "Masquer {count} événements", - "xpack.observabilityAiAssistant.chatCollapsedItems.showEvents": "Montrer {count} événements", - "xpack.observabilityAiAssistant.chatCollapsedItems.toggleButtonLabel": "Afficher/masquer les éléments", "xpack.observabilityAiAssistant.chatCompletionError.conversationNotFoundError": "Conversation introuvable", "xpack.observabilityAiAssistant.chatCompletionError.internalServerError": "Une erreur s'est produite au niveau du serveur interne", "xpack.observabilityAiAssistant.chatCompletionError.tokenLimitReachedError": "Limite de token atteinte. La limite de token est {tokenLimit}, mais la conversation actuelle a {tokenCount} tokens.", - "xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel": "Développer la liste des conversations", - "xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.newChatLabel": "Nouveau chat", - "xpack.observabilityAiAssistant.chatFlyout.euiFlyoutResizable.aiAssistantForObservabilityLabel": "Menu volant du chat de l'assistant d'IA pour Observability", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel": "Réduire la liste des conversations", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.expandConversationListLabel": "Développer la liste des conversations", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.newChatLabel": "Nouveau chat", - "xpack.observabilityAiAssistant.chatHeader.actions.connector": "Connecteur", - "xpack.observabilityAiAssistant.chatHeader.actions.copyConversation": "Copier la conversation", - "xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase": "Gérer la base de connaissances", - "xpack.observabilityAiAssistant.chatHeader.actions.settings": "Réglages de l'assistant d'IA", - "xpack.observabilityAiAssistant.chatHeader.actions.title": "Actions", - "xpack.observabilityAiAssistant.chatHeader.editConversationInput": "Modifier la conversation", - "xpack.observabilityAiAssistant.chatHeader.euiButtonIcon.navigateToConversationsLabel": "Accéder aux conversations", - "xpack.observabilityAiAssistant.chatHeader.euiButtonIcon.toggleFlyoutModeLabel": "Afficher / Masquer le mode menu volant", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock": "Ancrer le chat", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock": "Désancrer le chat", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.navigateToConversationsLabel": "Accéder aux conversations", - "xpack.observabilityAiAssistant.chatPromptEditor.codeEditor.payloadEditorLabel": "payloadEditor", - "xpack.observabilityAiAssistant.chatPromptEditor.euiButtonIcon.submitLabel": "Envoyer", "xpack.observabilityAiAssistant.chatService.div.helloLabel": "Bonjour", - "xpack.observabilityAiAssistant.chatTimeline.actions.copyMessage": "Copier le message", - "xpack.observabilityAiAssistant.chatTimeline.actions.copyMessageSuccessful": "Message copié", - "xpack.observabilityAiAssistant.chatTimeline.actions.editPrompt": "Modifier l'invite", - "xpack.observabilityAiAssistant.chatTimeline.actions.inspectPrompt": "Inspecter l'invite", - "xpack.observabilityAiAssistant.chatTimeline.messages.elasticAssistant.label": "Assistant d'Elastic", - "xpack.observabilityAiAssistant.chatTimeline.messages.system.label": "Système", - "xpack.observabilityAiAssistant.chatTimeline.messages.user.label": "Vous", - "xpack.observabilityAiAssistant.checkingKbAvailability": "Vérification de la disponibilité de la base de connaissances", "xpack.observabilityAiAssistant.connectorSelector.connectorSelectLabel": "Connecteur :", "xpack.observabilityAiAssistant.connectorSelector.empty": "Aucun connecteur", "xpack.observabilityAiAssistant.connectorSelector.error": "Impossible de charger les connecteurs", - "xpack.observabilityAiAssistant.conversationList.deleteConversationIconLabel": "Supprimer", - "xpack.observabilityAiAssistant.conversationList.errorMessage": "Échec de chargement", - "xpack.observabilityAiAssistant.conversationList.noConversations": "Aucune conversation", - "xpack.observabilityAiAssistant.conversationList.title": "Précédemment", "xpack.observabilityAiAssistant.conversationsDeepLinkTitle": "Conversations", - "xpack.observabilityAiAssistant.conversationStartTitle": "a démarré une conversation", - "xpack.observabilityAiAssistant.couldNotFindConversationContent": "Impossible de trouver une conversation avec l'ID {conversationId}. Assurez-vous que la conversation existe et que vous y avez accès.", - "xpack.observabilityAiAssistant.couldNotFindConversationTitle": "Conversation introuvable", - "xpack.observabilityAiAssistant.disclaimer.disclaimerLabel": "Ce chat est soutenu par une intégration avec votre fournisseur LLM. Il arrive que les grands modèles de langage (LLM) présentent comme correctes des informations incorrectes. Elastic prend en charge la configuration ainsi que la connexion au fournisseur LLM et à votre base de connaissances, mais n'est pas responsable des réponses fournies par le LLM.", - "xpack.observabilityAiAssistant.emptyConversationTitle": "Nouvelle conversation", - "xpack.observabilityAiAssistant.errorSettingUpKnowledgeBase": "Impossible de configurer la base de connaissances", - "xpack.observabilityAiAssistant.errorUpdatingConversation": "Impossible de mettre à jour la conversation", - "xpack.observabilityAiAssistant.executedFunctionFailureEvent": "impossible d'exécuter la fonction {functionName}", "xpack.observabilityAiAssistant.experimentalTitle": "Version d'évaluation technique", "xpack.observabilityAiAssistant.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 de corriger tout problème, mais les fonctionnalités des versions d'évaluation technique ne sont pas soumises aux SLA de support des fonctionnalités officielles en disponibilité générale.", "xpack.observabilityAiAssistant.failedLoadingResponseText": "Échec de chargement de la réponse", - "xpack.observabilityAiAssistant.failedToGetStatus": "Échec de l'obtention du statut du modèle.", "xpack.observabilityAiAssistant.failedToLoadResponse": "Échec du chargement d'une réponse de l'assistant d'intelligence artificielle", - "xpack.observabilityAiAssistant.failedToSetupKnowledgeBase": "Échec de la configuration de la base de connaissances.", "xpack.observabilityAiAssistant.featureRegistry.featureName": "Assistant d'intelligence artificielle d'Observability", "xpack.observabilityAiAssistant.feedbackButtons.em.thanksForYourFeedbackLabel": "Merci pour vos retours", - "xpack.observabilityAiAssistant.flyout.confirmDeleteButtonText": "Supprimer la conversation", - "xpack.observabilityAiAssistant.flyout.confirmDeleteConversationContent": "Cette action ne peut pas être annulée.", - "xpack.observabilityAiAssistant.flyout.confirmDeleteConversationTitle": "Supprimer cette conversation ?", - "xpack.observabilityAiAssistant.flyout.failedToDeleteConversation": "Impossible de supprimer la conversation", "xpack.observabilityAiAssistant.functionCallLimitExceeded": "\n\nRemarque : l'Assistant a essayé d'appeler une fonction, même si la limite a été dépassée", - "xpack.observabilityAiAssistant.functionListPopover.euiButtonIcon.selectAFunctionLabel": "Sélectionner la fonction", - "xpack.observabilityAiAssistant.functionListPopover.euiToolTip.clearFunction": "Effacer la fonction", - "xpack.observabilityAiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel": "Sélectionner une fonction", - "xpack.observabilityAiAssistant.hideExpandConversationButton.hide": "Masquer les chats", - "xpack.observabilityAiAssistant.hideExpandConversationButton.show": "Afficher les chats", - "xpack.observabilityAiAssistant.incorrectLicense.body": "Une licence d'entreprise est requise pour utiliser l'assistant d'intelligence artificielle d'Elastic.", - "xpack.observabilityAiAssistant.incorrectLicense.manageLicense": "Gérer la licence", - "xpack.observabilityAiAssistant.incorrectLicense.subscriptionPlansButton": "Plans d'abonnement", - "xpack.observabilityAiAssistant.incorrectLicense.title": "Mettez votre licence à niveau", - "xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel": "Configurer un connecteur GenAI", - "xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2": "Commencez à travailler avec l'assistant AI Elastic en configurant un connecteur pour votre fournisseur d'IA. Le modèle doit prendre en charge les appels de fonction. Lorsque vous utilisez OpenAI ou Azure, nous vous recommandons d'utiliser GPT4.", "xpack.observabilityAiAssistant.insight.actions": "Actions", "xpack.observabilityAiAssistant.insight.actions.connector": "Connecteur", "xpack.observabilityAiAssistant.insight.actions.editPrompt": "Modifier l'invite", @@ -32645,7 +32668,6 @@ "xpack.observabilityAiAssistant.insight.response.startChat": "Lancer le chat", "xpack.observabilityAiAssistant.insight.sendPromptEdit": "Envoyer l'invite", "xpack.observabilityAiAssistant.insightModifiedPrompt": "Cette information a été modifiée.", - "xpack.observabilityAiAssistant.installingKb": "Configuration de la base de connaissances", "xpack.observabilityAiAssistant.lensESQLFunction.displayChart": "Afficher le graphique", "xpack.observabilityAiAssistant.lensESQLFunction.displayTable": "Afficher le tableau", "xpack.observabilityAiAssistant.lensESQLFunction.edit": "Modifier la visualisation", @@ -32660,42 +32682,14 @@ "xpack.observabilityAiAssistant.missingCredentialsCallout.title": "Informations d'identification manquantes", "xpack.observabilityAiAssistant.navControl.initFailureErrorTitle": "Échec de l'initialisation de l'assistant d'IA d'Observability", "xpack.observabilityAiAssistant.navControl.openTheAIAssistantPopoverLabel": "Ouvrir l'assistant d'IA", - "xpack.observabilityAiAssistant.newChatButton": "Nouveau chat", - "xpack.observabilityAiAssistant.poweredByModel": "Alimenté par {model}", - "xpack.observabilityAiAssistant.prompt.functionList.filter": "Filtre", - "xpack.observabilityAiAssistant.prompt.functionList.functionList": "Liste de fonctions", - "xpack.observabilityAiAssistant.prompt.placeholder": "Envoyer un message à l'assistant", - "xpack.observabilityAiAssistant.promptEditorNaturalLanguage.euiSelectable.selectAnOptionLabel": "Sélectionner une option", "xpack.observabilityAiAssistant.regenerateResponseButtonLabel": "Régénérer", "xpack.observabilityAiAssistant.requiredConnectorField": "Connecteur obligatoire.", "xpack.observabilityAiAssistant.requiredMessageTextField": "Le message est requis.", "xpack.observabilityAiAssistant.resetDefaultPrompt": "Réinitialiser à la valeur par défaut", "xpack.observabilityAiAssistant.runThisQuery": "Afficher les résultats", - "xpack.observabilityAiAssistant.settingsPage.goToConnectorsButtonLabel": "Gérer les connecteurs", - "xpack.observabilityAiAssistant.setupKb": "Améliorez votre expérience en configurant la base de connaissances.", - "xpack.observabilityAiAssistant.simulatedFunctionCallingCalloutLabel": "L'appel de fonctions simulées est activé. Vous risquez de voir les performances se dégrader.", "xpack.observabilityAiAssistant.stopGeneratingButtonLabel": "Arrêter la génération", - "xpack.observabilityAiAssistant.suggestedFunctionEvent": "a demandé la fonction {functionName}", - "xpack.observabilityAiAssistant.technicalPreviewBadgeDescription": "GTP4 est nécessaire pour bénéficier d'une meilleure expérience avec les appels de fonctions (par exemple lors de la réalisation d'analyse de la cause d'un problème, de la visualisation de données et autres). GPT3.5 peut fonctionner pour certains des workflows les plus simples comme les explications d'erreurs ou pour bénéficier d'une expérience comparable à ChatGPT au sein de Kibana à partir du moment où les appels de fonctions ne sont pas fréquents.", "xpack.observabilityAiAssistant.tokenLimitError": "La conversation dépasse la limite de token. La limite de token maximale est **{tokenLimit}**, mais la conversation a **{tokenCount}** tokens. Veuillez démarrer une nouvelle conversation pour continuer.", - "xpack.observabilityAiAssistant.userExecutedFunctionEvent": "a exécuté la fonction {functionName}", - "xpack.observabilityAiAssistant.userSuggestedFunctionEvent": "a demandé la fonction {functionName}", "xpack.observabilityAiAssistant.visualizeThisQuery": "Visualiser cette requête", - "xpack.observabilityAiAssistant.welcomeMessage.div.checkTrainedModelsToLabel": " {retryInstallingLink} ou vérifiez {trainedModelsLink} pour vous assurer que {modelName} est déployé et en cours d'exécution.", - "xpack.observabilityAiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel": "Configuration de la base de connaissances", - "xpack.observabilityAiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel": "Inspecter les problèmes", - "xpack.observabilityAiAssistant.welcomeMessage.issuesDescriptionListTitleLabel": "Problèmes", - "xpack.observabilityAiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel": "La base de connaissances a été installée avec succès", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotDeployedLabel": "Le modèle {modelName} n'est pas déployé", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel": "L'état d'allocation de {modelName} est {allocationState}", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotStartedLabel": "L'état de déploiement de {modelName} est {deploymentState}", - "xpack.observabilityAiAssistant.welcomeMessage.retryButtonLabel": "Installer la base de connaissances", - "xpack.observabilityAiAssistant.welcomeMessage.trainedModelsLinkLabel": "Modèles entraînés", - "xpack.observabilityAiAssistant.welcomeMessage.weAreSettingUpTextLabel": "Nous configurons votre base de connaissances. Cette opération peut prendre quelques minutes. Vous pouvez continuer à utiliser l'Assistant lors de ce processus.", - "xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel": "Impossible de charger les connecteurs", - "xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel": "Vous n'avez pas les autorisations requises pour charger les connecteurs", - "xpack.observabilityAiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel": "Votre base de connaissances n'a pas été configurée.", - "xpack.observabilityAiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel": "Réessayer l'installation", "xpack.observabilityAiAssistantManagement.apmSettings.save.error": "Une erreur s'est produite lors de l'enregistrement des paramètres", "xpack.observabilityAiAssistantManagement.app.description": "Gérer l'Assistant d'IA pour Observability.", "xpack.observabilityAiAssistantManagement.app.title": "Assistant d'IA pour Observability", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8d1715916e755..f273895e8aa78 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9130,6 +9130,94 @@ "xpack.actions.serverSideErrors.unavailableLicenseInformationErrorMessage": "グラフを利用できません。現在ライセンス情報が利用できません。", "xpack.actions.subActionsFramework.urlValidationError": "URLの検証エラー:{message}", "xpack.actions.urlAllowedHostsConfigurationError": "ターゲット{field}「{value}」はKibana構成xpack.actions.allowedHostsに追加されていません", + "xpack.aiAssistant.askAssistantButton.buttonLabel": "アシスタントに聞く", + "xpack.aiAssistant.askAssistantButton.popoverContent": "Elastic Assistantでデータに関するインサイトを得ましょう", + "xpack.aiAssistant.assistantSetup.title": "Elastic AI Assistantへようこそ", + "xpack.aiAssistant.chatActionsMenu.euiButtonIcon.menuLabel": "メニュー", + "xpack.aiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel": "さらにアクションを表示", + "xpack.aiAssistant.chatCollapsedItems.hideEvents": "{count}件のイベントを非表示", + "xpack.aiAssistant.chatCollapsedItems.showEvents": "{count}件のイベントを表示", + "xpack.aiAssistant.chatCollapsedItems.toggleButtonLabel": "アイテムを表示/非表示", + "xpack.aiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel": "会話リストを展開", + "xpack.aiAssistant.chatFlyout.euiButtonIcon.newChatLabel": "新しいチャット", + "xpack.aiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel": "会話リストを折りたたむ", + "xpack.aiAssistant.chatFlyout.euiToolTip.expandConversationListLabel": "会話リストを展開", + "xpack.aiAssistant.chatFlyout.euiToolTip.newChatLabel": "新しいチャット", + "xpack.aiAssistant.chatHeader.actions.connector": "コネクター", + "xpack.aiAssistant.chatHeader.actions.copyConversation": "会話をコピー", + "xpack.aiAssistant.chatHeader.actions.knowledgeBase": "ナレッジベースを管理", + "xpack.aiAssistant.chatHeader.actions.settings": "AI Assistant設定", + "xpack.aiAssistant.chatHeader.actions.title": "アクション", + "xpack.aiAssistant.chatHeader.editConversationInput": "会話を編集", + "xpack.aiAssistant.chatHeader.euiButtonIcon.navigateToConversationsLabel": "会話に移動", + "xpack.aiAssistant.chatHeader.euiButtonIcon.toggleFlyoutModeLabel": "フライアウトモードを切り替え", + "xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock": "チャットを固定", + "xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock": "チャットを固定解除", + "xpack.aiAssistant.chatHeader.euiToolTip.navigateToConversationsLabel": "会話に移動", + "xpack.aiAssistant.chatPromptEditor.codeEditor.payloadEditorLabel": "payloadEditor", + "xpack.aiAssistant.chatPromptEditor.euiButtonIcon.submitLabel": "送信", + "xpack.aiAssistant.chatTimeline.actions.copyMessage": "メッセージをコピー", + "xpack.aiAssistant.chatTimeline.actions.copyMessageSuccessful": "コピーされたメッセージ", + "xpack.aiAssistant.chatTimeline.actions.editPrompt": "プロンプトを編集", + "xpack.aiAssistant.chatTimeline.actions.inspectPrompt": "プロンプトを検査", + "xpack.aiAssistant.chatTimeline.messages.elasticAssistant.label": "Elastic Assistant", + "xpack.aiAssistant.chatTimeline.messages.system.label": "システム", + "xpack.aiAssistant.chatTimeline.messages.user.label": "あなた", + "xpack.aiAssistant.checkingKbAvailability": "ナレッジベースの利用可能性を確認中", + "xpack.aiAssistant.conversationStartTitle": "会話を開始しました", + "xpack.aiAssistant.couldNotFindConversationContent": "id {conversationId}の会話が見つかりませんでした。会話が存在し、それにアクセスできることを確認してください。", + "xpack.aiAssistant.couldNotFindConversationTitle": "会話が見つかりません", + "xpack.aiAssistant.disclaimer.disclaimerLabel": "このチャットは、LLMプロバイダーとの統合によって提供されています。LLMは、正しくない情報を正しい情報であるかのように表示する場合があることが知られています。Elasticは、構成やLLMプロバイダーへの接続、お客様のナレッジベースへの接続はサポートしますが、LLMの応答については責任を負いません。", + "xpack.aiAssistant.emptyConversationTitle": "新しい会話", + "xpack.aiAssistant.errorSettingUpKnowledgeBase": "ナレッジベースをセットアップできませんでした", + "xpack.aiAssistant.errorUpdatingConversation": "会話を更新できませんでした", + "xpack.aiAssistant.executedFunctionFailureEvent": "関数{functionName}の実行に失敗しました", + "xpack.aiAssistant.failedToGetStatus": "モデルステータスを取得できませんでした。", + "xpack.aiAssistant.failedToSetupKnowledgeBase": "ナレッジベースをセットアップできませんでした。", + "xpack.aiAssistant.flyout.confirmDeleteButtonText": "会話を削除", + "xpack.aiAssistant.flyout.confirmDeleteConversationContent": "この操作は元に戻すことができません。", + "xpack.aiAssistant.flyout.confirmDeleteConversationTitle": "この会話を削除しますか?", + "xpack.aiAssistant.flyout.failedToDeleteConversation": "会話を削除できませんでした", + "xpack.aiAssistant.functionListPopover.euiButtonIcon.selectAFunctionLabel": "関数を選択", + "xpack.aiAssistant.functionListPopover.euiToolTip.clearFunction": "関数を消去", + "xpack.aiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel": "関数を選択", + "xpack.aiAssistant.hideExpandConversationButton.hide": "チャットを非表示", + "xpack.aiAssistant.hideExpandConversationButton.show": "チャットを表示", + "xpack.aiAssistant.incorrectLicense.body": "Elastic AI Assistantを使用するにはEnterpriseライセンスが必要です。", + "xpack.aiAssistant.incorrectLicense.manageLicense": "ライセンスの管理", + "xpack.aiAssistant.incorrectLicense.subscriptionPlansButton": "サブスクリプションオプション", + "xpack.aiAssistant.incorrectLicense.title": "ライセンスをアップグレード", + "xpack.aiAssistant.initialSetupPanel.setupConnector.buttonLabel": "GenAIコネクターをセットアップ", + "xpack.aiAssistant.initialSetupPanel.setupConnector.description2": "Elastic AI Assistantの使用を開始するには、AIプロバイダーのコネクターを設定します。モデルは関数呼び出しをサポートしている必要があります。OpenAIまたはAzureを使用するときには、GPT4を使用することをお勧めします。", + "xpack.aiAssistant.installingKb": "ナレッジベースをセットアップ中", + "xpack.aiAssistant.newChatButton": "新しいチャット", + "xpack.aiAssistant.poweredByModel": "{model}で駆動", + "xpack.aiAssistant.prompt.functionList.filter": "フィルター", + "xpack.aiAssistant.prompt.functionList.functionList": "関数リスト", + "xpack.aiAssistant.prompt.placeholder": "アシスタントにメッセージを送信", + "xpack.aiAssistant.promptEditorNaturalLanguage.euiSelectable.selectAnOptionLabel": "オプションを選択", + "xpack.aiAssistant.settingsPage.goToConnectorsButtonLabel": "コネクターを管理", + "xpack.aiAssistant.setupKb": "ナレッジベースを設定することで、エクスペリエンスが改善されます。", + "xpack.aiAssistant.simulatedFunctionCallingCalloutLabel": "シミュレートされた関数呼び出しが有効です。パフォーマンスが劣化する場合があります。", + "xpack.aiAssistant.suggestedFunctionEvent": "関数{functionName}を要求しました", + "xpack.aiAssistant.technicalPreviewBadgeDescription": "関数呼び出し(根本原因分析やデータの視覚化など)を使用する際に、より一貫性のあるエクスペリエンスを実現するために、GPT4が必要です。GPT3.5は、エラーの説明などのシンプルなワークフローの一部や、頻繁な関数呼び出しの使用が必要とされないKibana内のエクスペリエンスなどのChatGPTで機能します。", + "xpack.aiAssistant.userExecutedFunctionEvent": "関数{functionName}を実行しました", + "xpack.aiAssistant.userSuggestedFunctionEvent": "関数{functionName}を要求しました", + "xpack.aiAssistant.welcomeMessage.div.checkTrainedModelsToLabel": " {retryInstallingLink}か、{trainedModelsLink}を確認して、{modelName}がデプロイされ、実行中であることを確かめてください。", + "xpack.aiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel": "ナレッジベースをセットアップ中", + "xpack.aiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel": "問題を検査", + "xpack.aiAssistant.welcomeMessage.issuesDescriptionListTitleLabel": "問題", + "xpack.aiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel": "ナレッジベースは正常にインストールされました", + "xpack.aiAssistant.welcomeMessage.modelIsNotDeployedLabel": "モデル\"{modelName}\"はデプロイされていません", + "xpack.aiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel": "\"{modelName}\"の割り当て状態は{allocationState}です", + "xpack.aiAssistant.welcomeMessage.modelIsNotStartedLabel": "\"{modelName}\"のデプロイ状態は{deploymentState}です", + "xpack.aiAssistant.welcomeMessage.retryButtonLabel": "ナレッジベースをインストール", + "xpack.aiAssistant.welcomeMessage.trainedModelsLinkLabel": "学習済みモデル", + "xpack.aiAssistant.welcomeMessage.weAreSettingUpTextLabel": "ナレッジベースをセットアップしています。これには数分かかる場合があります。この処理の実行中には、アシスタントを使用し続けることができます。", + "xpack.aiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel": "コネクターを読み込めませんでした", + "xpack.aiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel": "コネクターを取得するために必要な権限が不足しています", + "xpack.aiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel": "ナレッジベースはセットアップされていません。", + "xpack.aiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel": "インストールを再試行", "xpack.aiops.actions.openChangePointInMlAppName": "AIOps Labsで開く", "xpack.aiops.analysis.columnSelectorAriaLabel": "列のフィルタリング", "xpack.aiops.analysis.columnSelectorNotEnoughColumnsSelected": "1つ以上の列を選択する必要があります。", @@ -32289,92 +32377,27 @@ "xpack.observabilityAiAssistant.app.starterPrompts.whatAreSlos.prompt": "SLOとは何ですか?", "xpack.observabilityAiAssistant.app.starterPrompts.whatAreSlos.title": "SLO", "xpack.observabilityAiAssistant.appTitle": "オブザーバビリティAI Assistant", - "xpack.observabilityAiAssistant.askAssistantButton.buttonLabel": "アシスタントに聞く", - "xpack.observabilityAiAssistant.askAssistantButton.popoverContent": "Elastic Assistantでデータに関するインサイトを得ましょう", - "xpack.observabilityAiAssistant.askAssistantButton.popoverTitle": "AI Assistant for Observability", - "xpack.observabilityAiAssistant.assistantSetup.title": "Elastic AI Assistantへようこそ", "xpack.observabilityAiAssistant.changesList.dotImpactHigh": "高", "xpack.observabilityAiAssistant.changesList.dotImpactLow": "低", "xpack.observabilityAiAssistant.changesList.dotImpactMedium": "中", "xpack.observabilityAiAssistant.changesList.labelColumnTitle": "ラベル", "xpack.observabilityAiAssistant.changesList.noChangesDetected": "変更が検出されません", "xpack.observabilityAiAssistant.changesList.trendColumnTitle": "傾向", - "xpack.observabilityAiAssistant.chatActionsMenu.euiButtonIcon.menuLabel": "メニュー", - "xpack.observabilityAiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel": "さらにアクションを表示", - "xpack.observabilityAiAssistant.chatCollapsedItems.hideEvents": "{count}件のイベントを非表示", - "xpack.observabilityAiAssistant.chatCollapsedItems.showEvents": "{count}件のイベントを表示", - "xpack.observabilityAiAssistant.chatCollapsedItems.toggleButtonLabel": "アイテムを表示/非表示", "xpack.observabilityAiAssistant.chatCompletionError.conversationNotFoundError": "会話が見つかりません", "xpack.observabilityAiAssistant.chatCompletionError.internalServerError": "内部サーバーエラーが発生しました", "xpack.observabilityAiAssistant.chatCompletionError.tokenLimitReachedError": "トークンの上限に達しました。トークンの上限は{tokenLimit}ですが、現在の会話には{tokenCount}個のトークンがあります。", - "xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel": "会話リストを展開", - "xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.newChatLabel": "新しいチャット", - "xpack.observabilityAiAssistant.chatFlyout.euiFlyoutResizable.aiAssistantForObservabilityLabel": "AI assistant for Observabilityチャットフライアウト", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel": "会話リストを折りたたむ", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.expandConversationListLabel": "会話リストを展開", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.newChatLabel": "新しいチャット", - "xpack.observabilityAiAssistant.chatHeader.actions.connector": "コネクター", - "xpack.observabilityAiAssistant.chatHeader.actions.copyConversation": "会話をコピー", - "xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase": "ナレッジベースを管理", - "xpack.observabilityAiAssistant.chatHeader.actions.settings": "AI Assistant設定", - "xpack.observabilityAiAssistant.chatHeader.actions.title": "アクション", - "xpack.observabilityAiAssistant.chatHeader.editConversationInput": "会話を編集", - "xpack.observabilityAiAssistant.chatHeader.euiButtonIcon.navigateToConversationsLabel": "会話に移動", - "xpack.observabilityAiAssistant.chatHeader.euiButtonIcon.toggleFlyoutModeLabel": "フライアウトモードを切り替え", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock": "チャットを固定", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock": "チャットを固定解除", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.navigateToConversationsLabel": "会話に移動", - "xpack.observabilityAiAssistant.chatPromptEditor.codeEditor.payloadEditorLabel": "payloadEditor", - "xpack.observabilityAiAssistant.chatPromptEditor.euiButtonIcon.submitLabel": "送信", "xpack.observabilityAiAssistant.chatService.div.helloLabel": "こんにちは", - "xpack.observabilityAiAssistant.chatTimeline.actions.copyMessage": "メッセージをコピー", - "xpack.observabilityAiAssistant.chatTimeline.actions.copyMessageSuccessful": "コピーされたメッセージ", - "xpack.observabilityAiAssistant.chatTimeline.actions.editPrompt": "プロンプトを編集", - "xpack.observabilityAiAssistant.chatTimeline.actions.inspectPrompt": "プロンプトを検査", - "xpack.observabilityAiAssistant.chatTimeline.messages.elasticAssistant.label": "Elastic Assistant", - "xpack.observabilityAiAssistant.chatTimeline.messages.system.label": "システム", - "xpack.observabilityAiAssistant.chatTimeline.messages.user.label": "あなた", - "xpack.observabilityAiAssistant.checkingKbAvailability": "ナレッジベースの利用可能性を確認中", "xpack.observabilityAiAssistant.connectorSelector.connectorSelectLabel": "コネクター:", "xpack.observabilityAiAssistant.connectorSelector.empty": "コネクターなし", "xpack.observabilityAiAssistant.connectorSelector.error": "コネクターを読み込めませんでした", - "xpack.observabilityAiAssistant.conversationList.deleteConversationIconLabel": "削除", - "xpack.observabilityAiAssistant.conversationList.errorMessage": "の読み込みに失敗しました", - "xpack.observabilityAiAssistant.conversationList.noConversations": "会話なし", - "xpack.observabilityAiAssistant.conversationList.title": "以前", "xpack.observabilityAiAssistant.conversationsDeepLinkTitle": "会話", - "xpack.observabilityAiAssistant.conversationStartTitle": "会話を開始しました", - "xpack.observabilityAiAssistant.couldNotFindConversationContent": "id {conversationId}の会話が見つかりませんでした。会話が存在し、それにアクセスできることを確認してください。", - "xpack.observabilityAiAssistant.couldNotFindConversationTitle": "会話が見つかりません", - "xpack.observabilityAiAssistant.disclaimer.disclaimerLabel": "このチャットは、LLMプロバイダーとの統合によって提供されています。LLMは、正しくない情報を正しい情報であるかのように表示する場合があることが知られています。Elasticは、構成やLLMプロバイダーへの接続、お客様のナレッジベースへの接続はサポートしますが、LLMの応答については責任を負いません。", - "xpack.observabilityAiAssistant.emptyConversationTitle": "新しい会話", - "xpack.observabilityAiAssistant.errorSettingUpKnowledgeBase": "ナレッジベースをセットアップできませんでした", - "xpack.observabilityAiAssistant.errorUpdatingConversation": "会話を更新できませんでした", - "xpack.observabilityAiAssistant.executedFunctionFailureEvent": "関数{functionName}の実行に失敗しました", "xpack.observabilityAiAssistant.experimentalTitle": "テクニカルプレビュー", "xpack.observabilityAiAssistant.experimentalTooltip": "この機能はテクニカルプレビュー中であり、将来のリリースでは変更されたり完全に削除されたりする場合があります。Elasticはすべての問題の修正に努めますが、テクニカルプレビュー中の機能には正式なGA機能のサポートSLAが適用されません。", "xpack.observabilityAiAssistant.failedLoadingResponseText": "応答の読み込みに失敗しました", - "xpack.observabilityAiAssistant.failedToGetStatus": "モデルステータスを取得できませんでした。", "xpack.observabilityAiAssistant.failedToLoadResponse": "AIアシスタントからの応答を読み込めませんでした", - "xpack.observabilityAiAssistant.failedToSetupKnowledgeBase": "ナレッジベースをセットアップできませんでした。", "xpack.observabilityAiAssistant.featureRegistry.featureName": "オブザーバビリティAI Assistant", "xpack.observabilityAiAssistant.feedbackButtons.em.thanksForYourFeedbackLabel": "フィードバックをご提供いただき、ありがとうございました。", - "xpack.observabilityAiAssistant.flyout.confirmDeleteButtonText": "会話を削除", - "xpack.observabilityAiAssistant.flyout.confirmDeleteConversationContent": "この操作は元に戻すことができません。", - "xpack.observabilityAiAssistant.flyout.confirmDeleteConversationTitle": "この会話を削除しますか?", - "xpack.observabilityAiAssistant.flyout.failedToDeleteConversation": "会話を削除できませんでした", "xpack.observabilityAiAssistant.functionCallLimitExceeded": "\n\n注:Assistantは、上限を超過しても、関数を呼び出そうとします。", - "xpack.observabilityAiAssistant.functionListPopover.euiButtonIcon.selectAFunctionLabel": "関数を選択", - "xpack.observabilityAiAssistant.functionListPopover.euiToolTip.clearFunction": "関数を消去", - "xpack.observabilityAiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel": "関数を選択", - "xpack.observabilityAiAssistant.hideExpandConversationButton.hide": "チャットを非表示", - "xpack.observabilityAiAssistant.hideExpandConversationButton.show": "チャットを表示", - "xpack.observabilityAiAssistant.incorrectLicense.body": "Elastic AI Assistantを使用するにはEnterpriseライセンスが必要です。", - "xpack.observabilityAiAssistant.incorrectLicense.manageLicense": "ライセンスの管理", - "xpack.observabilityAiAssistant.incorrectLicense.subscriptionPlansButton": "サブスクリプションオプション", - "xpack.observabilityAiAssistant.incorrectLicense.title": "ライセンスをアップグレード", - "xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel": "GenAIコネクターをセットアップ", - "xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2": "Elastic AI Assistantの使用を開始するには、AIプロバイダーのコネクターを設定します。モデルは関数呼び出しをサポートしている必要があります。OpenAIまたはAzureを使用するときには、GPT4を使用することをお勧めします。", "xpack.observabilityAiAssistant.insight.actions": "アクション", "xpack.observabilityAiAssistant.insight.actions.connector": "コネクター", "xpack.observabilityAiAssistant.insight.actions.editPrompt": "プロンプトを編集", @@ -32392,7 +32415,6 @@ "xpack.observabilityAiAssistant.insight.response.startChat": "チャットを開始", "xpack.observabilityAiAssistant.insight.sendPromptEdit": "プロンプトを送信", "xpack.observabilityAiAssistant.insightModifiedPrompt": "このインサイトは修正されました。", - "xpack.observabilityAiAssistant.installingKb": "ナレッジベースをセットアップ中", "xpack.observabilityAiAssistant.lensESQLFunction.displayChart": "グラフを表示", "xpack.observabilityAiAssistant.lensESQLFunction.displayTable": "表を表示", "xpack.observabilityAiAssistant.lensESQLFunction.edit": "ビジュアライゼーションを編集", @@ -32407,42 +32429,14 @@ "xpack.observabilityAiAssistant.missingCredentialsCallout.title": "資格情報がありません", "xpack.observabilityAiAssistant.navControl.initFailureErrorTitle": "オブザーバビリティAI Assistantを初期化できませんでした", "xpack.observabilityAiAssistant.navControl.openTheAIAssistantPopoverLabel": "AI Assistantを開く", - "xpack.observabilityAiAssistant.newChatButton": "新しいチャット", - "xpack.observabilityAiAssistant.poweredByModel": "{model}で駆動", - "xpack.observabilityAiAssistant.prompt.functionList.filter": "フィルター", - "xpack.observabilityAiAssistant.prompt.functionList.functionList": "関数リスト", - "xpack.observabilityAiAssistant.prompt.placeholder": "アシスタントにメッセージを送信", - "xpack.observabilityAiAssistant.promptEditorNaturalLanguage.euiSelectable.selectAnOptionLabel": "オプションを選択", "xpack.observabilityAiAssistant.regenerateResponseButtonLabel": "再生成", "xpack.observabilityAiAssistant.requiredConnectorField": "コネクターが必要です。", "xpack.observabilityAiAssistant.requiredMessageTextField": "メッセージが必要です。", "xpack.observabilityAiAssistant.resetDefaultPrompt": "デフォルトにリセット", "xpack.observabilityAiAssistant.runThisQuery": "結果を表示", - "xpack.observabilityAiAssistant.settingsPage.goToConnectorsButtonLabel": "コネクターを管理", - "xpack.observabilityAiAssistant.setupKb": "ナレッジベースを設定することで、エクスペリエンスが改善されます。", - "xpack.observabilityAiAssistant.simulatedFunctionCallingCalloutLabel": "シミュレートされた関数呼び出しが有効です。パフォーマンスが劣化する場合があります。", "xpack.observabilityAiAssistant.stopGeneratingButtonLabel": "生成を停止", - "xpack.observabilityAiAssistant.suggestedFunctionEvent": "関数{functionName}を要求しました", - "xpack.observabilityAiAssistant.technicalPreviewBadgeDescription": "関数呼び出し(根本原因分析やデータの視覚化など)を使用する際に、より一貫性のあるエクスペリエンスを実現するために、GPT4が必要です。GPT3.5は、エラーの説明などのシンプルなワークフローの一部や、頻繁な関数呼び出しの使用が必要とされないKibana内のエクスペリエンスなどのChatGPTで機能します。", "xpack.observabilityAiAssistant.tokenLimitError": "会話はトークンの上限を超えました。トークンの最大上限は**{tokenLimit}**ですが、現在の会話には**{tokenCount}**個のトークンがあります。続行するには、新しい会話を開始してください。", - "xpack.observabilityAiAssistant.userExecutedFunctionEvent": "関数{functionName}を実行しました", - "xpack.observabilityAiAssistant.userSuggestedFunctionEvent": "関数{functionName}を要求しました", "xpack.observabilityAiAssistant.visualizeThisQuery": "このクエリーを可視化", - "xpack.observabilityAiAssistant.welcomeMessage.div.checkTrainedModelsToLabel": " {retryInstallingLink}か、{trainedModelsLink}を確認して、{modelName}がデプロイされ、実行中であることを確かめてください。", - "xpack.observabilityAiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel": "ナレッジベースをセットアップ中", - "xpack.observabilityAiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel": "問題を検査", - "xpack.observabilityAiAssistant.welcomeMessage.issuesDescriptionListTitleLabel": "問題", - "xpack.observabilityAiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel": "ナレッジベースは正常にインストールされました", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotDeployedLabel": "モデル\"{modelName}\"はデプロイされていません", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel": "\"{modelName}\"の割り当て状態は{allocationState}です", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotStartedLabel": "\"{modelName}\"のデプロイ状態は{deploymentState}です", - "xpack.observabilityAiAssistant.welcomeMessage.retryButtonLabel": "ナレッジベースをインストール", - "xpack.observabilityAiAssistant.welcomeMessage.trainedModelsLinkLabel": "学習済みモデル", - "xpack.observabilityAiAssistant.welcomeMessage.weAreSettingUpTextLabel": "ナレッジベースをセットアップしています。これには数分かかる場合があります。この処理の実行中には、アシスタントを使用し続けることができます。", - "xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel": "コネクターを読み込めませんでした", - "xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel": "コネクターを取得するために必要な権限が不足しています", - "xpack.observabilityAiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel": "ナレッジベースはセットアップされていません。", - "xpack.observabilityAiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel": "インストールを再試行", "xpack.observabilityAiAssistantManagement.apmSettings.save.error": "設定の保存中にエラーが発生しました", "xpack.observabilityAiAssistantManagement.app.description": "AI Assistant for Observabilityを管理します。", "xpack.observabilityAiAssistantManagement.app.title": "AI Assistant for Observability", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e5b0a3c4aea3d..176cde8a39d13 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9147,6 +9147,94 @@ "xpack.actions.serverSideErrors.unavailableLicenseInformationErrorMessage": "操作不可用 - 许可信息当前不可用。", "xpack.actions.subActionsFramework.urlValidationError": "验证 URL 时出错:{message}", "xpack.actions.urlAllowedHostsConfigurationError": "目标 {field} 的“{value}”未添加到 Kibana 配置 xpack.actions.allowedHosts", + "xpack.aiAssistant.askAssistantButton.buttonLabel": "询问助手", + "xpack.aiAssistant.askAssistantButton.popoverContent": "使用 Elastic 助手深入了解您的数据", + "xpack.aiAssistant.assistantSetup.title": "欢迎使用 Elastic AI 助手", + "xpack.aiAssistant.chatActionsMenu.euiButtonIcon.menuLabel": "菜单", + "xpack.aiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel": "更多操作", + "xpack.aiAssistant.chatCollapsedItems.hideEvents": "隐藏 {count} 个事件", + "xpack.aiAssistant.chatCollapsedItems.showEvents": "显示 {count} 个事件", + "xpack.aiAssistant.chatCollapsedItems.toggleButtonLabel": "显示/隐藏项目", + "xpack.aiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel": "展开对话列表", + "xpack.aiAssistant.chatFlyout.euiButtonIcon.newChatLabel": "新聊天", + "xpack.aiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel": "折叠对话列表", + "xpack.aiAssistant.chatFlyout.euiToolTip.expandConversationListLabel": "展开对话列表", + "xpack.aiAssistant.chatFlyout.euiToolTip.newChatLabel": "新聊天", + "xpack.aiAssistant.chatHeader.actions.connector": "连接器", + "xpack.aiAssistant.chatHeader.actions.copyConversation": "复制对话", + "xpack.aiAssistant.chatHeader.actions.knowledgeBase": "管理知识库", + "xpack.aiAssistant.chatHeader.actions.settings": "AI 助手设置", + "xpack.aiAssistant.chatHeader.actions.title": "操作", + "xpack.aiAssistant.chatHeader.editConversationInput": "编辑对话", + "xpack.aiAssistant.chatHeader.euiButtonIcon.navigateToConversationsLabel": "导航到对话", + "xpack.aiAssistant.chatHeader.euiButtonIcon.toggleFlyoutModeLabel": "切换浮出控件模式", + "xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock": "停靠聊天", + "xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock": "取消停靠聊天", + "xpack.aiAssistant.chatHeader.euiToolTip.navigateToConversationsLabel": "导航到对话", + "xpack.aiAssistant.chatPromptEditor.codeEditor.payloadEditorLabel": "payloadEditor", + "xpack.aiAssistant.chatPromptEditor.euiButtonIcon.submitLabel": "提交", + "xpack.aiAssistant.chatTimeline.actions.copyMessage": "复制消息", + "xpack.aiAssistant.chatTimeline.actions.copyMessageSuccessful": "已复制消息", + "xpack.aiAssistant.chatTimeline.actions.editPrompt": "编辑提示", + "xpack.aiAssistant.chatTimeline.actions.inspectPrompt": "检查提示", + "xpack.aiAssistant.chatTimeline.messages.elasticAssistant.label": "Elastic 助手", + "xpack.aiAssistant.chatTimeline.messages.system.label": "系统", + "xpack.aiAssistant.chatTimeline.messages.user.label": "您", + "xpack.aiAssistant.checkingKbAvailability": "正在检查知识库的可用性", + "xpack.aiAssistant.conversationStartTitle": "已开始对话", + "xpack.aiAssistant.couldNotFindConversationContent": "找不到 ID 为 {conversationId} 的对话。请确保该对话存在并且您具有访问权限。", + "xpack.aiAssistant.couldNotFindConversationTitle": "未找到对话", + "xpack.aiAssistant.disclaimer.disclaimerLabel": "通过集成 LLM 提供商来支持此聊天。众所周知,LLM 有时会提供错误信息,好像它是正确的。Elastic 支持配置并连接到 LLM 提供商和知识库,但不对 LLM 响应负责。", + "xpack.aiAssistant.emptyConversationTitle": "新对话", + "xpack.aiAssistant.errorSettingUpKnowledgeBase": "无法设置知识库", + "xpack.aiAssistant.errorUpdatingConversation": "无法更新对话", + "xpack.aiAssistant.executedFunctionFailureEvent": "无法执行函数 {functionName}", + "xpack.aiAssistant.failedToGetStatus": "无法获取模型状态。", + "xpack.aiAssistant.failedToSetupKnowledgeBase": "无法设置知识库。", + "xpack.aiAssistant.flyout.confirmDeleteButtonText": "删除对话", + "xpack.aiAssistant.flyout.confirmDeleteConversationContent": "此操作无法撤消。", + "xpack.aiAssistant.flyout.confirmDeleteConversationTitle": "删除此对话?", + "xpack.aiAssistant.flyout.failedToDeleteConversation": "无法删除对话", + "xpack.aiAssistant.functionListPopover.euiButtonIcon.selectAFunctionLabel": "选择函数", + "xpack.aiAssistant.functionListPopover.euiToolTip.clearFunction": "清除函数", + "xpack.aiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel": "选择函数", + "xpack.aiAssistant.hideExpandConversationButton.hide": "隐藏聊天", + "xpack.aiAssistant.hideExpandConversationButton.show": "显示聊天", + "xpack.aiAssistant.incorrectLicense.body": "您需要企业级许可证才能使用 Elastic AI 助手。", + "xpack.aiAssistant.incorrectLicense.manageLicense": "管理许可证", + "xpack.aiAssistant.incorrectLicense.subscriptionPlansButton": "订阅计划", + "xpack.aiAssistant.incorrectLicense.title": "升级您的许可证", + "xpack.aiAssistant.initialSetupPanel.setupConnector.buttonLabel": "设置 GenAI 连接器", + "xpack.aiAssistant.initialSetupPanel.setupConnector.description2": "通过为您的 AI 提供商设置连接器,开始使用 Elastic AI 助手。此模型需要支持函数调用。使用 OpenAI 或 Azure 时,建议使用 GPT4。", + "xpack.aiAssistant.installingKb": "正在设置知识库", + "xpack.aiAssistant.newChatButton": "新聊天", + "xpack.aiAssistant.poweredByModel": "由 {model} 提供支持", + "xpack.aiAssistant.prompt.functionList.filter": "筛选", + "xpack.aiAssistant.prompt.functionList.functionList": "函数列表", + "xpack.aiAssistant.prompt.placeholder": "向助手发送消息", + "xpack.aiAssistant.promptEditorNaturalLanguage.euiSelectable.selectAnOptionLabel": "选择选项", + "xpack.aiAssistant.settingsPage.goToConnectorsButtonLabel": "管理连接器", + "xpack.aiAssistant.setupKb": "通过设置知识库来改进体验。", + "xpack.aiAssistant.simulatedFunctionCallingCalloutLabel": "模拟函数调用已启用。您可能会面临性能降级。", + "xpack.aiAssistant.suggestedFunctionEvent": "已请求函数 {functionName}", + "xpack.aiAssistant.technicalPreviewBadgeDescription": "需要 GPT4 以在使用函数调用时(例如,执行根本原因分析、数据可视化等时候)获得更加一致的体验。GPT3.5 可作用于某些更简单的工作流(如解释错误),或在 Kibana 中获得不需要频繁使用函数调用的与 ChatGPT 类似的体验。", + "xpack.aiAssistant.userExecutedFunctionEvent": "已执行函数 {functionName}", + "xpack.aiAssistant.userSuggestedFunctionEvent": "已请求函数 {functionName}", + "xpack.aiAssistant.welcomeMessage.div.checkTrainedModelsToLabel": " {retryInstallingLink} 或检查 {trainedModelsLink},确保 {modelName} 已部署并正在运行。", + "xpack.aiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel": "正在设置知识库", + "xpack.aiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel": "检查问题", + "xpack.aiAssistant.welcomeMessage.issuesDescriptionListTitleLabel": "问题", + "xpack.aiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel": "已成功安装知识库", + "xpack.aiAssistant.welcomeMessage.modelIsNotDeployedLabel": "未部署模型 {modelName}", + "xpack.aiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel": "{modelName} 的分配状态为 {allocationState}", + "xpack.aiAssistant.welcomeMessage.modelIsNotStartedLabel": "{modelName} 的部署状态为 {deploymentState}", + "xpack.aiAssistant.welcomeMessage.retryButtonLabel": "安装知识库", + "xpack.aiAssistant.welcomeMessage.trainedModelsLinkLabel": "已训练模型", + "xpack.aiAssistant.welcomeMessage.weAreSettingUpTextLabel": "我们正在设置您的知识库。这可能需要若干分钟。此进程处于运行状态时,您可以继续使用该助手。", + "xpack.aiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel": "无法加载连接器", + "xpack.aiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel": "缺少获取连接器所需的权限", + "xpack.aiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel": "尚未设置您的知识库。", + "xpack.aiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel": "重试安装", "xpack.aiops.actions.openChangePointInMlAppName": "在 Aiops 实验室中打开", "xpack.aiops.analysis.columnSelectorAriaLabel": "筛选列", "xpack.aiops.analysis.columnSelectorNotEnoughColumnsSelected": "必须至少选择一列。", @@ -32331,92 +32419,27 @@ "xpack.observabilityAiAssistant.app.starterPrompts.whatAreSlos.prompt": "什么是 SLO?", "xpack.observabilityAiAssistant.app.starterPrompts.whatAreSlos.title": "SLO", "xpack.observabilityAiAssistant.appTitle": "Observability AI 助手", - "xpack.observabilityAiAssistant.askAssistantButton.buttonLabel": "询问助手", - "xpack.observabilityAiAssistant.askAssistantButton.popoverContent": "使用 Elastic 助手深入了解您的数据", - "xpack.observabilityAiAssistant.askAssistantButton.popoverTitle": "适用于 Observability 的 AI 助手", - "xpack.observabilityAiAssistant.assistantSetup.title": "欢迎使用 Elastic AI 助手", "xpack.observabilityAiAssistant.changesList.dotImpactHigh": "高", "xpack.observabilityAiAssistant.changesList.dotImpactLow": "低", "xpack.observabilityAiAssistant.changesList.dotImpactMedium": "中", "xpack.observabilityAiAssistant.changesList.labelColumnTitle": "标签", "xpack.observabilityAiAssistant.changesList.noChangesDetected": "未检测到更改", "xpack.observabilityAiAssistant.changesList.trendColumnTitle": "趋势", - "xpack.observabilityAiAssistant.chatActionsMenu.euiButtonIcon.menuLabel": "菜单", - "xpack.observabilityAiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel": "更多操作", - "xpack.observabilityAiAssistant.chatCollapsedItems.hideEvents": "隐藏 {count} 个事件", - "xpack.observabilityAiAssistant.chatCollapsedItems.showEvents": "显示 {count} 个事件", - "xpack.observabilityAiAssistant.chatCollapsedItems.toggleButtonLabel": "显示/隐藏项目", "xpack.observabilityAiAssistant.chatCompletionError.conversationNotFoundError": "未找到对话", "xpack.observabilityAiAssistant.chatCompletionError.internalServerError": "发生内部服务器错误", "xpack.observabilityAiAssistant.chatCompletionError.tokenLimitReachedError": "达到了词元限制。词元限制为 {tokenLimit},但当前对话具有 {tokenCount} 个词元。", - "xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel": "展开对话列表", - "xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.newChatLabel": "新聊天", - "xpack.observabilityAiAssistant.chatFlyout.euiFlyoutResizable.aiAssistantForObservabilityLabel": "适用于 Observability 聊天浮出控件的 AI 助手", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel": "折叠对话列表", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.expandConversationListLabel": "展开对话列表", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.newChatLabel": "新聊天", - "xpack.observabilityAiAssistant.chatHeader.actions.connector": "连接器", - "xpack.observabilityAiAssistant.chatHeader.actions.copyConversation": "复制对话", - "xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase": "管理知识库", - "xpack.observabilityAiAssistant.chatHeader.actions.settings": "AI 助手设置", - "xpack.observabilityAiAssistant.chatHeader.actions.title": "操作", - "xpack.observabilityAiAssistant.chatHeader.editConversationInput": "编辑对话", - "xpack.observabilityAiAssistant.chatHeader.euiButtonIcon.navigateToConversationsLabel": "导航到对话", - "xpack.observabilityAiAssistant.chatHeader.euiButtonIcon.toggleFlyoutModeLabel": "切换浮出控件模式", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock": "停靠聊天", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock": "取消停靠聊天", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.navigateToConversationsLabel": "导航到对话", - "xpack.observabilityAiAssistant.chatPromptEditor.codeEditor.payloadEditorLabel": "payloadEditor", - "xpack.observabilityAiAssistant.chatPromptEditor.euiButtonIcon.submitLabel": "提交", "xpack.observabilityAiAssistant.chatService.div.helloLabel": "您好", - "xpack.observabilityAiAssistant.chatTimeline.actions.copyMessage": "复制消息", - "xpack.observabilityAiAssistant.chatTimeline.actions.copyMessageSuccessful": "已复制消息", - "xpack.observabilityAiAssistant.chatTimeline.actions.editPrompt": "编辑提示", - "xpack.observabilityAiAssistant.chatTimeline.actions.inspectPrompt": "检查提示", - "xpack.observabilityAiAssistant.chatTimeline.messages.elasticAssistant.label": "Elastic 助手", - "xpack.observabilityAiAssistant.chatTimeline.messages.system.label": "系统", - "xpack.observabilityAiAssistant.chatTimeline.messages.user.label": "您", - "xpack.observabilityAiAssistant.checkingKbAvailability": "正在检查知识库的可用性", "xpack.observabilityAiAssistant.connectorSelector.connectorSelectLabel": "连接器:", "xpack.observabilityAiAssistant.connectorSelector.empty": "无连接器", "xpack.observabilityAiAssistant.connectorSelector.error": "无法加载连接器", - "xpack.observabilityAiAssistant.conversationList.deleteConversationIconLabel": "删除", - "xpack.observabilityAiAssistant.conversationList.errorMessage": "无法加载", - "xpack.observabilityAiAssistant.conversationList.noConversations": "无对话", - "xpack.observabilityAiAssistant.conversationList.title": "以前", "xpack.observabilityAiAssistant.conversationsDeepLinkTitle": "对话", - "xpack.observabilityAiAssistant.conversationStartTitle": "已开始对话", - "xpack.observabilityAiAssistant.couldNotFindConversationContent": "找不到 ID 为 {conversationId} 的对话。请确保该对话存在并且您具有访问权限。", - "xpack.observabilityAiAssistant.couldNotFindConversationTitle": "未找到对话", - "xpack.observabilityAiAssistant.disclaimer.disclaimerLabel": "通过集成 LLM 提供商来支持此聊天。众所周知,LLM 有时会提供错误信息,好像它是正确的。Elastic 支持配置并连接到 LLM 提供商和知识库,但不对 LLM 响应负责。", - "xpack.observabilityAiAssistant.emptyConversationTitle": "新对话", - "xpack.observabilityAiAssistant.errorSettingUpKnowledgeBase": "无法设置知识库", - "xpack.observabilityAiAssistant.errorUpdatingConversation": "无法更新对话", - "xpack.observabilityAiAssistant.executedFunctionFailureEvent": "无法执行函数 {functionName}", "xpack.observabilityAiAssistant.experimentalTitle": "技术预览", "xpack.observabilityAiAssistant.experimentalTooltip": "此功能处于技术预览状态,在未来版本中可能会更改或完全移除。Elastic 将努力修复任何问题,但处于技术预览状态的功能不受正式 GA 功能支持 SLA 的约束。", "xpack.observabilityAiAssistant.failedLoadingResponseText": "无法加载响应", - "xpack.observabilityAiAssistant.failedToGetStatus": "无法获取模型状态。", "xpack.observabilityAiAssistant.failedToLoadResponse": "无法加载来自 AI 助手的响应", - "xpack.observabilityAiAssistant.failedToSetupKnowledgeBase": "无法设置知识库。", "xpack.observabilityAiAssistant.featureRegistry.featureName": "Observability AI 助手", "xpack.observabilityAiAssistant.feedbackButtons.em.thanksForYourFeedbackLabel": "感谢您提供反馈", - "xpack.observabilityAiAssistant.flyout.confirmDeleteButtonText": "删除对话", - "xpack.observabilityAiAssistant.flyout.confirmDeleteConversationContent": "此操作无法撤消。", - "xpack.observabilityAiAssistant.flyout.confirmDeleteConversationTitle": "删除此对话?", - "xpack.observabilityAiAssistant.flyout.failedToDeleteConversation": "无法删除对话", "xpack.observabilityAiAssistant.functionCallLimitExceeded": "\n\n注意:即使超出了限制,助手仍尝试调用了函数", - "xpack.observabilityAiAssistant.functionListPopover.euiButtonIcon.selectAFunctionLabel": "选择函数", - "xpack.observabilityAiAssistant.functionListPopover.euiToolTip.clearFunction": "清除函数", - "xpack.observabilityAiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel": "选择函数", - "xpack.observabilityAiAssistant.hideExpandConversationButton.hide": "隐藏聊天", - "xpack.observabilityAiAssistant.hideExpandConversationButton.show": "显示聊天", - "xpack.observabilityAiAssistant.incorrectLicense.body": "您需要企业级许可证才能使用 Elastic AI 助手。", - "xpack.observabilityAiAssistant.incorrectLicense.manageLicense": "管理许可证", - "xpack.observabilityAiAssistant.incorrectLicense.subscriptionPlansButton": "订阅计划", - "xpack.observabilityAiAssistant.incorrectLicense.title": "升级您的许可证", - "xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel": "设置 GenAI 连接器", - "xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2": "通过为您的 AI 提供商设置连接器,开始使用 Elastic AI 助手。此模型需要支持函数调用。使用 OpenAI 或 Azure 时,建议使用 GPT4。", "xpack.observabilityAiAssistant.insight.actions": "操作", "xpack.observabilityAiAssistant.insight.actions.connector": "连接器", "xpack.observabilityAiAssistant.insight.actions.editPrompt": "编辑提示", @@ -32434,7 +32457,6 @@ "xpack.observabilityAiAssistant.insight.response.startChat": "开始聊天", "xpack.observabilityAiAssistant.insight.sendPromptEdit": "发送提示", "xpack.observabilityAiAssistant.insightModifiedPrompt": "此洞察已被修改。", - "xpack.observabilityAiAssistant.installingKb": "正在设置知识库", "xpack.observabilityAiAssistant.lensESQLFunction.displayChart": "显示图表", "xpack.observabilityAiAssistant.lensESQLFunction.displayTable": "显示表", "xpack.observabilityAiAssistant.lensESQLFunction.edit": "编辑可视化", @@ -32449,42 +32471,14 @@ "xpack.observabilityAiAssistant.missingCredentialsCallout.title": "凭据缺失", "xpack.observabilityAiAssistant.navControl.initFailureErrorTitle": "无法初始化 Observability AI 助手", "xpack.observabilityAiAssistant.navControl.openTheAIAssistantPopoverLabel": "打开 AI 助手", - "xpack.observabilityAiAssistant.newChatButton": "新聊天", - "xpack.observabilityAiAssistant.poweredByModel": "由 {model} 提供支持", - "xpack.observabilityAiAssistant.prompt.functionList.filter": "筛选", - "xpack.observabilityAiAssistant.prompt.functionList.functionList": "函数列表", - "xpack.observabilityAiAssistant.prompt.placeholder": "向助手发送消息", - "xpack.observabilityAiAssistant.promptEditorNaturalLanguage.euiSelectable.selectAnOptionLabel": "选择选项", "xpack.observabilityAiAssistant.regenerateResponseButtonLabel": "重新生成", "xpack.observabilityAiAssistant.requiredConnectorField": "“连接器”必填。", "xpack.observabilityAiAssistant.requiredMessageTextField": "“消息”必填。", "xpack.observabilityAiAssistant.resetDefaultPrompt": "重置为默认值", "xpack.observabilityAiAssistant.runThisQuery": "显示结果", - "xpack.observabilityAiAssistant.settingsPage.goToConnectorsButtonLabel": "管理连接器", - "xpack.observabilityAiAssistant.setupKb": "通过设置知识库来改进体验。", - "xpack.observabilityAiAssistant.simulatedFunctionCallingCalloutLabel": "模拟函数调用已启用。您可能会面临性能降级。", "xpack.observabilityAiAssistant.stopGeneratingButtonLabel": "停止生成", - "xpack.observabilityAiAssistant.suggestedFunctionEvent": "已请求函数 {functionName}", - "xpack.observabilityAiAssistant.technicalPreviewBadgeDescription": "需要 GPT4 以在使用函数调用时(例如,执行根本原因分析、数据可视化等时候)获得更加一致的体验。GPT3.5 可作用于某些更简单的工作流(如解释错误),或在 Kibana 中获得不需要频繁使用函数调用的与 ChatGPT 类似的体验。", "xpack.observabilityAiAssistant.tokenLimitError": "此对话已超出词元限制。最大词元限制为 **{tokenLimit}**,但当前对话具有 **{tokenCount}** 个词元。请启动新对话以继续。", - "xpack.observabilityAiAssistant.userExecutedFunctionEvent": "已执行函数 {functionName}", - "xpack.observabilityAiAssistant.userSuggestedFunctionEvent": "已请求函数 {functionName}", "xpack.observabilityAiAssistant.visualizeThisQuery": "可视化此查询", - "xpack.observabilityAiAssistant.welcomeMessage.div.checkTrainedModelsToLabel": " {retryInstallingLink} 或检查 {trainedModelsLink},确保 {modelName} 已部署并正在运行。", - "xpack.observabilityAiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel": "正在设置知识库", - "xpack.observabilityAiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel": "检查问题", - "xpack.observabilityAiAssistant.welcomeMessage.issuesDescriptionListTitleLabel": "问题", - "xpack.observabilityAiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel": "已成功安装知识库", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotDeployedLabel": "未部署模型 {modelName}", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel": "{modelName} 的分配状态为 {allocationState}", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotStartedLabel": "{modelName} 的部署状态为 {deploymentState}", - "xpack.observabilityAiAssistant.welcomeMessage.retryButtonLabel": "安装知识库", - "xpack.observabilityAiAssistant.welcomeMessage.trainedModelsLinkLabel": "已训练模型", - "xpack.observabilityAiAssistant.welcomeMessage.weAreSettingUpTextLabel": "我们正在设置您的知识库。这可能需要若干分钟。此进程处于运行状态时,您可以继续使用该助手。", - "xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel": "无法加载连接器", - "xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel": "缺少获取连接器所需的权限", - "xpack.observabilityAiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel": "尚未设置您的知识库。", - "xpack.observabilityAiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel": "重试安装", "xpack.observabilityAiAssistantManagement.apmSettings.save.error": "保存设置时出错", "xpack.observabilityAiAssistantManagement.app.description": "管理适用于 Observability 的 AI 助手。", "xpack.observabilityAiAssistantManagement.app.title": "适用于 Observability 的 AI 助手", diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts b/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts index 51f98b5389a9d..3ad0ef88ef75a 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts @@ -6,59 +6,12 @@ */ import type { Agent as SuperTestAgent } from 'supertest'; -import { Client } from '@elastic/elasticsearch'; -import expect from '@kbn/expect'; + import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; -import type { IndexDetails } from '@kbn/cloud-security-posture-common'; import { CLOUD_SECURITY_PLUGIN_VERSION } from '@kbn/cloud-security-posture-plugin/common/constants'; import { SecurityService } from '@kbn/ftr-common-functional-ui-services'; import { RoleCredentials } from '@kbn/ftr-common-functional-services'; -export const deleteIndex = async (es: Client, indexToBeDeleted: string[]) => { - return Promise.all([ - ...indexToBeDeleted.map((indexes) => - es.deleteByQuery({ - index: indexes, - query: { - match_all: {}, - }, - ignore_unavailable: true, - refresh: true, - }) - ), - ]); -}; - -export const bulkIndex = async (es: Client, findingsMock: T[], indexName: string) => { - const operations = findingsMock.flatMap((finding) => [ - { create: { _index: indexName } }, // Action description - { - ...finding, - '@timestamp': new Date().toISOString(), - }, // Data to index - ]); - - await es.bulk({ - body: operations, // Bulk API expects 'body' for operations - refresh: true, - }); -}; - -export const addIndex = async (es: Client, findingsMock: T[], indexName: string) => { - await Promise.all([ - ...findingsMock.map((finding) => - es.index({ - index: indexName, - body: { - ...finding, - '@timestamp': new Date().toISOString(), - }, - refresh: true, - }) - ), - ]); -}; - export async function createPackagePolicy( supertest: SuperTestAgent, agentPolicyId: string, @@ -233,10 +186,10 @@ export const createUser = async (security: SecurityService, userName: string, ro }); }; -export const createCSPOnlyRole = async ( +export const createCSPRole = async ( security: SecurityService, roleName: string, - indicesName: string + indicesName?: string[] ) => { await security.role.create(roleName, { kibana: [ @@ -245,12 +198,12 @@ export const createCSPOnlyRole = async ( spaces: ['*'], }, ], - ...(indicesName.length !== 0 + ...(indicesName && indicesName.length > 0 ? { elasticsearch: { indices: [ { - names: [indicesName], + names: indicesName, privileges: ['read'], }, ], @@ -267,15 +220,3 @@ export const deleteRole = async (security: SecurityService, roleName: string) => export const deleteUser = async (security: SecurityService, userName: string) => { await security.user.delete(userName); }; - -export const assertIndexStatus = ( - indicesDetails: IndexDetails[], - indexName: string, - expectedStatus: string -) => { - const actualValue = indicesDetails.find((idx) => idx.index === indexName)?.status; - expect(actualValue).to.eql( - expectedStatus, - `expected ${indexName} status to be ${expectedStatus} but got ${actualValue} instead` - ); -}; diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_index_timeout.ts b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_index_timeout.ts index ce0c9014478dc..a2949a9f35253 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_index_timeout.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_index_timeout.ts @@ -13,16 +13,10 @@ import { LATEST_FINDINGS_INDEX_DEFAULT_NS, VULNERABILITIES_INDEX_DEFAULT_NS, } from '@kbn/cloud-security-posture-plugin/common/constants'; +import { EsIndexDataProvider } from '../../../../cloud_security_posture_api/utils'; import { generateAgent } from '../../../../fleet_api_integration/helpers'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { deleteIndex, createPackagePolicy } from '../helper'; - -const INDEX_ARRAY = [ - FINDINGS_INDEX_DEFAULT_NS, - LATEST_FINDINGS_INDEX_DEFAULT_NS, - CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, - VULNERABILITIES_INDEX_DEFAULT_NS, -]; +import { createPackagePolicy } from '../helper'; const currentTimeMinusFourHours = new Date(Date.now() - 21600000).toISOString(); const currentTimeMinusTenMinutes = new Date(Date.now() - 600000).toISOString(); @@ -35,6 +29,13 @@ export default function (providerContext: FtrProviderContext) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const fleetAndAgents = getService('fleetAndAgents'); + const findingsIndex = new EsIndexDataProvider(es, FINDINGS_INDEX_DEFAULT_NS); + const latestFindingsIndex = new EsIndexDataProvider(es, LATEST_FINDINGS_INDEX_DEFAULT_NS); + const vulnerabilitiesIndex = new EsIndexDataProvider(es, VULNERABILITIES_INDEX_DEFAULT_NS); + const cdrVulnerabilitiesIndex = new EsIndexDataProvider( + es, + CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN + ); describe('GET /internal/cloud_security_posture/status', () => { let agentPolicyId: string; @@ -84,12 +85,20 @@ export default function (providerContext: FtrProviderContext) { .expect(200); await generateAgent(providerContext, 'healthy', `Agent policy test 2`, agentPolicyId); - await deleteIndex(es, INDEX_ARRAY); + await findingsIndex.deleteAll(); + await latestFindingsIndex.deleteAll(); + await vulnerabilitiesIndex.deleteAll(); + await cdrVulnerabilitiesIndex.deleteAll(); }); afterEach(async () => { await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); + + await findingsIndex.deleteAll(); + await latestFindingsIndex.deleteAll(); + await vulnerabilitiesIndex.deleteAll(); + await cdrVulnerabilitiesIndex.deleteAll(); }); it(`Should return index-timeout when installed kspm, has findings only on logs-cloud_security_posture.findings-default* and it has been more than 10 minutes since the installation`, async () => { diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexed.ts b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexed.ts index 504bb9f504516..ec8b6a09f8bb2 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexed.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexed.ts @@ -8,28 +8,25 @@ import expect from '@kbn/expect'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import { CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN } from '@kbn/cloud-security-posture-common'; import type { CspSetupStatus } from '@kbn/cloud-security-posture-common'; -import { - FINDINGS_INDEX_DEFAULT_NS, - LATEST_FINDINGS_INDEX_DEFAULT_NS, - VULNERABILITIES_INDEX_DEFAULT_NS, -} from '@kbn/cloud-security-posture-plugin/common/constants'; +import { LATEST_FINDINGS_INDEX_DEFAULT_NS } from '@kbn/cloud-security-posture-plugin/common/constants'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { deleteIndex, addIndex, createPackagePolicy } from '../helper'; +import { EsIndexDataProvider } from '../../../../cloud_security_posture_api/utils'; +import { createPackagePolicy } from '../helper'; import { findingsMockData, vulnerabilityMockData } from '../mock_data'; -const INDEX_ARRAY = [ - FINDINGS_INDEX_DEFAULT_NS, - LATEST_FINDINGS_INDEX_DEFAULT_NS, - CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, - VULNERABILITIES_INDEX_DEFAULT_NS, -]; - export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const supertest = getService('supertest'); const es = getService('es'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); + const latestFindingsIndex = new EsIndexDataProvider(es, LATEST_FINDINGS_INDEX_DEFAULT_NS); + const latestVulnerabilitiesIndex = new EsIndexDataProvider( + es, + CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN + ); + const mock3PIndex = 'security_solution-mock-3p-integration.misconfiguration_latest'; + const _3pIndex = new EsIndexDataProvider(es, mock3PIndex); describe('GET /internal/cloud_security_posture/status', () => { let agentPolicyId: string; @@ -50,19 +47,21 @@ export default function (providerContext: FtrProviderContext) { agentPolicyId = agentPolicyResponse.item.id; - await deleteIndex(es, INDEX_ARRAY); - await addIndex(es, findingsMockData, LATEST_FINDINGS_INDEX_DEFAULT_NS); - await addIndex(es, vulnerabilityMockData, CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN); + await latestFindingsIndex.deleteAll(); + await latestVulnerabilitiesIndex.deleteAll(); + await _3pIndex.deleteAll(); }); afterEach(async () => { - await deleteIndex(es, INDEX_ARRAY); + await latestFindingsIndex.deleteAll(); + await latestVulnerabilitiesIndex.deleteAll(); + await _3pIndex.destroyIndex(); await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); }); it(`Return hasMisconfigurationsFindings true when there are latest findings but no installed integrations`, async () => { - await addIndex(es, findingsMockData, LATEST_FINDINGS_INDEX_DEFAULT_NS); + await latestFindingsIndex.addBulk(findingsMockData); const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) @@ -77,9 +76,7 @@ export default function (providerContext: FtrProviderContext) { }); it(`Return hasMisconfigurationsFindings true when there are only findings in third party index`, async () => { - await deleteIndex(es, INDEX_ARRAY); - const mock3PIndex = 'security_solution-mock-3p-integration.misconfiguration_latest'; - await addIndex(es, findingsMockData, mock3PIndex); + await _3pIndex.addBulk(findingsMockData); const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) @@ -91,13 +88,9 @@ export default function (providerContext: FtrProviderContext) { true, `expected hasMisconfigurationsFindings to be true but got ${res.hasMisconfigurationsFindings} instead` ); - - await deleteIndex(es, [mock3PIndex]); }); it(`Return hasMisconfigurationsFindings false when there are no findings`, async () => { - await deleteIndex(es, INDEX_ARRAY); - const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') @@ -120,6 +113,8 @@ export default function (providerContext: FtrProviderContext) { 'kspm' ); + await latestFindingsIndex.addBulk(findingsMockData); + const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') @@ -142,6 +137,8 @@ export default function (providerContext: FtrProviderContext) { 'cspm' ); + await latestFindingsIndex.addBulk(findingsMockData); + const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') @@ -164,6 +161,8 @@ export default function (providerContext: FtrProviderContext) { 'vuln_mgmt' ); + await latestVulnerabilitiesIndex.addBulk(vulnerabilityMockData); + const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexing.ts b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexing.ts index 4d66d8460b9a4..16ee02083e34c 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexing.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexing.ts @@ -7,29 +7,23 @@ import expect from '@kbn/expect'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { CspSetupStatus } from '@kbn/cloud-security-posture-common'; -import { CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN } from '@kbn/cloud-security-posture-common'; import { FINDINGS_INDEX_DEFAULT_NS, - LATEST_FINDINGS_INDEX_DEFAULT_NS, VULNERABILITIES_INDEX_DEFAULT_NS, } from '@kbn/cloud-security-posture-plugin/common/constants'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { deleteIndex, addIndex, createPackagePolicy } from '../helper'; +import { EsIndexDataProvider } from '../../../../cloud_security_posture_api/utils'; +import { createPackagePolicy } from '../helper'; import { findingsMockData, vulnerabilityMockData } from '../mock_data'; -const INDEX_ARRAY = [ - FINDINGS_INDEX_DEFAULT_NS, - LATEST_FINDINGS_INDEX_DEFAULT_NS, - CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, - VULNERABILITIES_INDEX_DEFAULT_NS, -]; - export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const supertest = getService('supertest'); const es = getService('es'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); + const findingsIndex = new EsIndexDataProvider(es, FINDINGS_INDEX_DEFAULT_NS); + const vulnerabilitiesIndex = new EsIndexDataProvider(es, VULNERABILITIES_INDEX_DEFAULT_NS); describe('GET /internal/cloud_security_posture/status', () => { let agentPolicyId: string; @@ -49,13 +43,13 @@ export default function (providerContext: FtrProviderContext) { }); agentPolicyId = agentPolicyResponse.item.id; - await deleteIndex(es, INDEX_ARRAY); - await addIndex(es, findingsMockData, FINDINGS_INDEX_DEFAULT_NS); - await addIndex(es, vulnerabilityMockData, VULNERABILITIES_INDEX_DEFAULT_NS); + await findingsIndex.deleteAll(); + await vulnerabilitiesIndex.deleteAll(); }); afterEach(async () => { - await deleteIndex(es, INDEX_ARRAY); + await findingsIndex.deleteAll(); + await vulnerabilitiesIndex.deleteAll(); await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); }); @@ -70,6 +64,8 @@ export default function (providerContext: FtrProviderContext) { 'kspm' ); + await findingsIndex.addBulk(findingsMockData); + const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') @@ -92,6 +88,8 @@ export default function (providerContext: FtrProviderContext) { 'cspm' ); + await findingsIndex.addBulk(findingsMockData); + const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') @@ -114,6 +112,8 @@ export default function (providerContext: FtrProviderContext) { 'vuln_mgmt' ); + await vulnerabilitiesIndex.addBulk(vulnerabilityMockData); + const { body: res }: { body: CspSetupStatus } = await supertest .get(`/internal/cloud_security_posture/status`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_unprivileged.ts b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_unprivileged.ts index 7c09e4b51f679..5d0f6207e904a 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_unprivileged.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_unprivileged.ts @@ -13,16 +13,9 @@ import { LATEST_FINDINGS_INDEX_DEFAULT_NS, FINDINGS_INDEX_PATTERN, } from '@kbn/cloud-security-posture-plugin/common/constants'; +import { find, without } from 'lodash'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { - createPackagePolicy, - createUser, - createCSPOnlyRole, - deleteRole, - deleteUser, - deleteIndex, - assertIndexStatus, -} from '../helper'; +import { createPackagePolicy, createUser, createCSPRole, deleteRole, deleteUser } from '../helper'; const UNPRIVILEGED_ROLE = 'unprivileged_test_role'; const UNPRIVILEGED_USERNAME = 'unprivileged_test_user'; @@ -32,27 +25,36 @@ export default function (providerContext: FtrProviderContext) { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); - const es = getService('es'); const kibanaServer = getService('kibanaServer'); const security = getService('security'); + const allIndices = [ + LATEST_FINDINGS_INDEX_DEFAULT_NS, + FINDINGS_INDEX_PATTERN, + BENCHMARK_SCORE_INDEX_DEFAULT_NS, + CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, + ]; + describe('GET /internal/cloud_security_posture/status', () => { let agentPolicyId: string; describe('STATUS = UNPRIVILEGED TEST', () => { before(async () => { - await createCSPOnlyRole(security, UNPRIVILEGED_ROLE, ''); + await createCSPRole(security, UNPRIVILEGED_ROLE); await createUser(security, UNPRIVILEGED_USERNAME, UNPRIVILEGED_ROLE); + await esArchiver.loadIfNeeded( + 'x-pack/test/functional/es_archives/fleet/empty_fleet_server' + ); }); after(async () => { await deleteUser(security, UNPRIVILEGED_USERNAME); await deleteRole(security, UNPRIVILEGED_ROLE); + await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); }); beforeEach(async () => { await kibanaServer.savedObjects.cleanStandardList(); - await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); const { body: agentPolicyResponse } = await supertest .post(`/api/fleet/agent_policies`) @@ -67,7 +69,6 @@ export default function (providerContext: FtrProviderContext) { }); afterEach(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); await kibanaServer.savedObjects.cleanStandardList(); }); @@ -106,7 +107,6 @@ export default function (providerContext: FtrProviderContext) { describe('status = unprivileged test indices', () => { beforeEach(async () => { await kibanaServer.savedObjects.cleanStandardList(); - await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); const { body: agentPolicyResponse } = await supertest .post(`/api/fleet/agent_policies`) @@ -124,11 +124,21 @@ export default function (providerContext: FtrProviderContext) { await deleteUser(security, UNPRIVILEGED_USERNAME); await deleteRole(security, UNPRIVILEGED_ROLE); await kibanaServer.savedObjects.cleanStandardList(); + }); + + before(async () => { + await esArchiver.loadIfNeeded( + 'x-pack/test/functional/es_archives/fleet/empty_fleet_server' + ); + }); + + after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); }); it(`Return unprivileged when missing access to findings_latest index`, async () => { - await createCSPOnlyRole(security, UNPRIVILEGED_ROLE, LATEST_FINDINGS_INDEX_DEFAULT_NS); + const privilegedIndices = without(allIndices, LATEST_FINDINGS_INDEX_DEFAULT_NS); + await createCSPRole(security, UNPRIVILEGED_ROLE, privilegedIndices); await createUser(security, UNPRIVILEGED_USERNAME, UNPRIVILEGED_ROLE); await createPackagePolicy( @@ -149,30 +159,30 @@ export default function (providerContext: FtrProviderContext) { expect(res.kspm.status).to.eql( 'unprivileged', - `expected unprivileged but got ${res.kspm.status} instead` + `kspm status expected unprivileged but got ${res.kspm.status} instead` ); expect(res.cspm.status).to.eql( 'unprivileged', - `expected unprivileged but got ${res.cspm.status} instead` + `cspm status expected unprivileged but got ${res.cspm.status} instead` ); expect(res.vuln_mgmt.status).to.eql( - 'unprivileged', - `expected unprivileged but got ${res.vuln_mgmt.status} instead` + 'not-installed', + `cnvm status expected not_installed but got ${res.vuln_mgmt.status} instead` ); - assertIndexStatus(res.indicesDetails, LATEST_FINDINGS_INDEX_DEFAULT_NS, 'empty'); - assertIndexStatus(res.indicesDetails, FINDINGS_INDEX_PATTERN, 'empty'); - assertIndexStatus(res.indicesDetails, BENCHMARK_SCORE_INDEX_DEFAULT_NS, 'unprivileged'); - assertIndexStatus( - res.indicesDetails, - CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, + expect(res).to.have.property('indicesDetails'); + expect(find(res.indicesDetails, { index: LATEST_FINDINGS_INDEX_DEFAULT_NS })?.status).eql( 'unprivileged' ); + + privilegedIndices.forEach((index) => { + expect(find(res.indicesDetails, { index })?.status).not.eql('unprivileged'); + }); }); it(`Return unprivileged when missing access to score index`, async () => { - await deleteIndex(es, [BENCHMARK_SCORE_INDEX_DEFAULT_NS]); - await createCSPOnlyRole(security, UNPRIVILEGED_ROLE, BENCHMARK_SCORE_INDEX_DEFAULT_NS); + const privilegedIndices = without(allIndices, BENCHMARK_SCORE_INDEX_DEFAULT_NS); + await createCSPRole(security, UNPRIVILEGED_ROLE, privilegedIndices); await createUser(security, UNPRIVILEGED_USERNAME, UNPRIVILEGED_ROLE); await createPackagePolicy( @@ -193,33 +203,33 @@ export default function (providerContext: FtrProviderContext) { expect(res.kspm.status).to.eql( 'unprivileged', - `expected unprivileged but got ${res.kspm.status} instead` + `kspm status expected unprivileged but got ${res.kspm.status} instead` ); expect(res.cspm.status).to.eql( 'unprivileged', - `expected unprivileged but got ${res.cspm.status} instead` + `cspm status expected unprivileged but got ${res.cspm.status} instead` ); expect(res.vuln_mgmt.status).to.eql( 'unprivileged', - `expected unprivileged but got ${res.vuln_mgmt.status} instead` + `cnvm status expected unprivileged but got ${res.vuln_mgmt.status} instead` ); - assertIndexStatus(res.indicesDetails, LATEST_FINDINGS_INDEX_DEFAULT_NS, 'unprivileged'); - assertIndexStatus(res.indicesDetails, FINDINGS_INDEX_PATTERN, 'empty'); - assertIndexStatus(res.indicesDetails, BENCHMARK_SCORE_INDEX_DEFAULT_NS, 'empty'); - assertIndexStatus( - res.indicesDetails, - CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, + expect(res).to.have.property('indicesDetails'); + expect(find(res.indicesDetails, { index: BENCHMARK_SCORE_INDEX_DEFAULT_NS })?.status).eql( 'unprivileged' ); + + privilegedIndices.forEach((index) => { + expect(find(res.indicesDetails, { index })?.status).not.eql('unprivileged'); + }); }); it(`Return unprivileged when missing access to vulnerabilities_latest index`, async () => { - await createCSPOnlyRole( - security, - UNPRIVILEGED_ROLE, + const privilegedIndices = without( + allIndices, CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN ); + await createCSPRole(security, UNPRIVILEGED_ROLE, privilegedIndices); await createUser(security, UNPRIVILEGED_USERNAME, UNPRIVILEGED_ROLE); await createPackagePolicy( @@ -239,26 +249,27 @@ export default function (providerContext: FtrProviderContext) { .expect(200); expect(res.kspm.status).to.eql( - 'unprivileged', - `expected unprivileged but got ${res.kspm.status} instead` + 'not-deployed', + `kspm status expected unprivileged but got ${res.kspm.status} instead` ); expect(res.cspm.status).to.eql( - 'unprivileged', - `expected unprivileged but got ${res.cspm.status} instead` + 'not-installed', + `cspm status expected unprivileged but got ${res.cspm.status} instead` ); expect(res.vuln_mgmt.status).to.eql( 'unprivileged', - `expected unprivileged but got ${res.vuln_mgmt.status} instead` + `cnvm status expected unprivileged but got ${res.vuln_mgmt.status} instead` ); - assertIndexStatus(res.indicesDetails, LATEST_FINDINGS_INDEX_DEFAULT_NS, 'unprivileged'); - assertIndexStatus(res.indicesDetails, FINDINGS_INDEX_PATTERN, 'empty'); - assertIndexStatus(res.indicesDetails, BENCHMARK_SCORE_INDEX_DEFAULT_NS, 'unprivileged'); - assertIndexStatus( - res.indicesDetails, - CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, - 'empty' - ); + expect(res).to.have.property('indicesDetails'); + expect( + find(res.indicesDetails, { index: CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN }) + ?.status + ).eql('unprivileged'); + + privilegedIndices.forEach((index) => { + expect(find(res.indicesDetails, { index })?.status).not.eql('unprivileged'); + }); }); }); }); diff --git a/x-pack/test/api_integration/apis/management/index_management/data_enrichers/ilm.ts b/x-pack/test/api_integration/apis/management/index_management/data_enrichers/ilm.ts index 234a1518a9c59..3ae9b554bf3ee 100644 --- a/x-pack/test/api_integration/apis/management/index_management/data_enrichers/ilm.ts +++ b/x-pack/test/api_integration/apis/management/index_management/data_enrichers/ilm.ts @@ -55,16 +55,17 @@ export default function ({ getService }: FtrProviderContext) { const testAlias = 'test_alias'; const testIlmPolicy = 'test_policy'; describe('GET indices with data enrichers', () => { - before(async () => { + beforeEach(async () => { await createIndex(testIndex); - await createIlmPolicy('test_policy'); - await addPolicyToIndex(testIlmPolicy, testIndex, testAlias); }); - after(async () => { + afterEach(async () => { await esDeleteAllIndices([testIndex]); }); it(`ILM data is fetched by the ILM data enricher`, async () => { + await createIlmPolicy('test_policy'); + await addPolicyToIndex(testIlmPolicy, testIndex, testAlias); + const { body: indices } = await supertest .get(`${API_BASE_PATH}/indices`) .set('kbn-xsrf', 'xxx') @@ -75,5 +76,18 @@ export default function ({ getService }: FtrProviderContext) { const { ilm } = index; expect(ilm.policy).to.eql(testIlmPolicy); }); + + it(`ILM data is not empty even if the index unmanaged`, async () => { + const { body: indices } = await supertest + .get(`${API_BASE_PATH}/indices`) + .set('kbn-xsrf', 'xxx') + .expect(200); + + const index = indices.find((item: Index) => item.name === testIndex); + + const { ilm } = index; + expect(ilm.index).to.eql(testIndex); + expect(ilm.managed).to.eql(false); + }); }); } diff --git a/x-pack/test/api_integration/apis/ml/anomaly_detectors/forecast_with_spaces.ts b/x-pack/test/api_integration/apis/ml/anomaly_detectors/forecast_with_spaces.ts index a330edd9a41d7..32e82c67e348d 100644 --- a/x-pack/test/api_integration/apis/ml/anomaly_detectors/forecast_with_spaces.ts +++ b/x-pack/test/api_integration/apis/ml/anomaly_detectors/forecast_with_spaces.ts @@ -58,7 +58,8 @@ export default ({ getService }: FtrProviderContext) => { return body; } - describe('POST anomaly_detectors _forecast with spaces', function () { + // Failing see: https://github.com/elastic/kibana/issues/195602 + describe.skip('POST anomaly_detectors _forecast with spaces', function () { let forecastId: string; before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); diff --git a/x-pack/test/cloud_security_posture_api/utils.ts b/x-pack/test/cloud_security_posture_api/utils.ts index 6f0d86419a349..9f0805c2e85c1 100644 --- a/x-pack/test/cloud_security_posture_api/utils.ts +++ b/x-pack/test/cloud_security_posture_api/utils.ts @@ -23,7 +23,7 @@ export const waitForPluginInitialized = ({ }: { retry: RetryService; logger: ToolingLog; - supertest: Agent; + supertest: Pick; }): Promise => retry.try(async () => { logger.debug('Check CSP plugin is initialized'); @@ -44,13 +44,16 @@ export class EsIndexDataProvider { this.index = index; } - addBulk(docs: Array>, overrideTimestamp = true) { + async addBulk(docs: Array>, overrideTimestamp = true) { const operations = docs.flatMap((doc) => [ - { index: { _index: this.index } }, + { create: { _index: this.index } }, { ...doc, ...(overrideTimestamp ? { '@timestamp': new Date().toISOString() } : {}) }, ]); - return this.es.bulk({ refresh: 'wait_for', index: this.index, operations }); + const resp = await this.es.bulk({ refresh: 'wait_for', index: this.index, operations }); + expect(resp.errors).eql(false, `Error in bulk indexing: ${JSON.stringify(resp)}`); + + return resp; } async deleteAll() { diff --git a/x-pack/test/encrypted_saved_objects_api_integration/config.ts b/x-pack/test/encrypted_saved_objects_api_integration/config.ts index 0b2585a94e954..433cbb9e36151 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/config.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/config.ts @@ -6,7 +6,9 @@ */ import path from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/encrypted_saved_objects_api_integration/ftr_provider_context.d.ts b/x-pack/test/encrypted_saved_objects_api_integration/ftr_provider_context.d.ts index aa56557c09df8..b05bbc8f6318d 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/ftr_provider_context.d.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/ftr_provider_context.d.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { GenericFtrProviderContext } from '@kbn/test'; +import type { GenericFtrProviderContext } from '@kbn/test'; -import { services } from './services'; +import type { services } from './services'; export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin/server/hidden_saved_object_routes.ts b/x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin/server/hidden_saved_object_routes.ts index 363d463139e4c..d0edb19e6a639 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin/server/hidden_saved_object_routes.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin/server/hidden_saved_object_routes.ts @@ -6,8 +6,9 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter, CoreSetup, SavedObject } from '@kbn/core/server'; -import { PluginsSetup, PluginsStart } from '.'; +import type { CoreSetup, IRouter, SavedObject } from '@kbn/core/server'; + +import type { PluginsSetup, PluginsStart } from '.'; export function registerHiddenSORoutes( router: IRouter, diff --git a/x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin/server/index.ts b/x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin/server/index.ts index f73d88fc79746..c7946b2e68131 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin/server/index.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin/server/index.ts @@ -5,20 +5,21 @@ * 2.0. */ -import { deepFreeze } from '@kbn/std'; -import { +import { schema } from '@kbn/config-schema'; +import type { CoreSetup, PluginInitializer, + SavedObject, SavedObjectsNamespaceType, SavedObjectUnsanitizedDoc, - SavedObject, } from '@kbn/core/server'; -import { schema } from '@kbn/config-schema'; -import { +import type { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart, } from '@kbn/encrypted-saved-objects-plugin/server'; -import { SpacesPluginSetup } from '@kbn/spaces-plugin/server'; +import type { SpacesPluginSetup } from '@kbn/spaces-plugin/server'; +import { deepFreeze } from '@kbn/std'; + import { registerHiddenSORoutes } from './hidden_saved_object_routes'; const SAVED_OBJECT_WITH_SECRET_TYPE = 'saved-object-with-secret'; diff --git a/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_aad_include_list.ts b/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_aad_include_list.ts index fd6c0305f88ce..ae80cae697db9 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_aad_include_list.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_aad_include_list.ts @@ -7,7 +7,8 @@ import { MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; + +import type { FtrProviderContext } from '../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts b/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts index 62a9545afa77d..4687a01858260 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts @@ -5,13 +5,12 @@ * 2.0. */ -import expect from '@kbn/expect'; import type { SavedObject } from '@kbn/core/server'; import { MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; -import { - descriptorToArray, - SavedObjectDescriptor, -} from '@kbn/encrypted-saved-objects-plugin/server/crypto'; +import type { SavedObjectDescriptor } from '@kbn/encrypted-saved-objects-plugin/server/crypto'; +import { descriptorToArray } from '@kbn/encrypted-saved-objects-plugin/server/crypto'; +import expect from '@kbn/expect'; + import type { FtrProviderContext } from '../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_decryption.ts b/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_decryption.ts index 8b0471cd34a9a..99428c2f0797f 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_decryption.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_decryption.ts @@ -7,7 +7,8 @@ import { MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; + +import type { FtrProviderContext } from '../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/encrypted_saved_objects_api_integration/tests/index.ts b/x-pack/test/encrypted_saved_objects_api_integration/tests/index.ts index c3188d0f5d0c3..1b14ec04811cd 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/tests/index.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/tests/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import type { FtrProviderContext } from '../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('encryptedSavedObjects', function encryptedSavedObjectsSuite() { diff --git a/x-pack/test/fleet_cypress/cli_config.space_awareness.ts b/x-pack/test/fleet_cypress/cli_config.space_awareness.ts new file mode 100644 index 0000000000000..2fbaca2da9eca --- /dev/null +++ b/x-pack/test/fleet_cypress/cli_config.space_awareness.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 { FtrConfigProviderContext } from '@kbn/test'; + +import { FleetCypressCliTestRunner } from './runner'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const cypressConfig = await readConfigFile(require.resolve('./config.space_awareness.ts')); + return { + ...cypressConfig.getAll(), + + testRunner: FleetCypressCliTestRunner, + }; +} diff --git a/x-pack/test/fleet_cypress/config.space_awareness.ts b/x-pack/test/fleet_cypress/config.space_awareness.ts new file mode 100644 index 0000000000000..eeee016b0c4d3 --- /dev/null +++ b/x-pack/test/fleet_cypress/config.space_awareness.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext, getKibanaCliLoggers } from '@kbn/test'; +import { CA_CERT_PATH } from '@kbn/dev-utils'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const kibanaCommonTestsConfig = await readConfigFile( + require.resolve('@kbn/test-suites-src/common/config') + ); + const xpackFunctionalTestsConfig = await readConfigFile( + require.resolve('../functional/config.base.js') + ); + + return { + ...kibanaCommonTestsConfig.getAll(), + + esTestCluster: { + ...xpackFunctionalTestsConfig.get('esTestCluster'), + serverArgs: [ + ...xpackFunctionalTestsConfig.get('esTestCluster.serverArgs'), + // define custom es server here + // API Keys is enabled at the top level + 'xpack.security.enabled=true', + 'http.host=0.0.0.0', + ], + }, + + kbnTestServer: { + ...xpackFunctionalTestsConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalTestsConfig.get('kbnTestServer.serverArgs'), + '--csp.warnLegacyBrowsers=false', + '--csp.strict=false', + // define custom kibana server args here + `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, + + // add feature flags here + `--xpack.fleet.enableExperimental=${JSON.stringify([ + 'agentTamperProtectionEnabled', + 'subfeaturePrivileges', + 'useSpaceAwareness', + ])}`, + + `--logging.loggers=${JSON.stringify([ + ...getKibanaCliLoggers(xpackFunctionalTestsConfig.get('kbnTestServer.serverArgs')), + + // Enable debug fleet logs by default + { + name: 'plugins.fleet', + level: 'debug', + appenders: ['default'], + }, + ])}`, + ], + }, + }; +} diff --git a/x-pack/test/functional/apps/infra/logs/log_entry_categories_tab.ts b/x-pack/test/functional/apps/infra/logs/log_entry_categories_tab.ts index 33396497fc83c..0d4a5440ebd58 100644 --- a/x-pack/test/functional/apps/infra/logs/log_entry_categories_tab.ts +++ b/x-pack/test/functional/apps/infra/logs/log_entry_categories_tab.ts @@ -9,14 +9,54 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -export default ({ getService }: FtrProviderContext) => { +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const PageObjects = getPageObjects(['security']); const esArchiver = getService('esArchiver'); const logsUi = getService('logsUi'); const retry = getService('retry'); + const security = getService('security'); describe('Log Entry Categories Tab', function () { this.tags('includeFirefox'); + const loginWithMLPrivileges = async (privileges: Record) => { + await security.role.create('global_logs_role', { + elasticsearch: { + cluster: ['all'], + indices: [{ names: ['*'], privileges: ['read', 'view_index_metadata'] }], + }, + kibana: [ + { + feature: { + logs: ['read'], + ...privileges, + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create('global_logs_read_user', { + password: 'global_logs_read_user-password', + roles: ['global_logs_role'], + full_name: 'logs test user', + }); + + await PageObjects.security.forceLogout(); + + await PageObjects.security.login('global_logs_read_user', 'global_logs_read_user-password', { + expectSpaceSelector: false, + }); + }; + + const logoutAndDeleteUser = async () => { + await PageObjects.security.forceLogout(); + await Promise.all([ + security.role.delete('global_logs_role'), + security.user.delete('global_logs_read_user'), + ]); + }; + describe('with a trial license', () => { it('Shows no data page when indices do not exist', async () => { await logsUi.logEntryCategoriesPage.navigateTo(); @@ -26,14 +66,42 @@ export default ({ getService }: FtrProviderContext) => { }); }); - it('shows setup page when indices exist', async () => { - await esArchiver.load('x-pack/test/functional/es_archives/infra/simple_logs'); - await logsUi.logEntryCategoriesPage.navigateTo(); + describe('when indices exists', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + }); - await retry.try(async () => { - expect(await logsUi.logEntryCategoriesPage.getSetupScreen()).to.be.ok(); + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + }); + + it('shows setup page when indices exist', async () => { + await logsUi.logEntryCategoriesPage.navigateTo(); + + await retry.try(async () => { + expect(await logsUi.logEntryCategoriesPage.getSetupScreen()).to.be.ok(); + }); + }); + + it('shows required ml read privileges prompt when the user has not any ml privileges', async () => { + await loginWithMLPrivileges({}); + await logsUi.logEntryCategoriesPage.navigateTo(); + + await retry.try(async () => { + expect(await logsUi.logEntryCategoriesPage.getNoMlReadPrivilegesPrompt()).to.be.ok(); + }); + await logoutAndDeleteUser(); + }); + + it('shows required ml all privileges prompt when the user has only ml read privileges', async () => { + await loginWithMLPrivileges({ ml: ['read'] }); + await logsUi.logEntryCategoriesPage.navigateTo(); + + await retry.try(async () => { + expect(await logsUi.logEntryCategoriesPage.getNoMlAllPrivilegesPrompt()).to.be.ok(); + }); + await logoutAndDeleteUser(); }); - await esArchiver.unload('x-pack/test/functional/es_archives/infra/simple_logs'); }); }); }); diff --git a/x-pack/test/functional/apps/infra/logs/log_entry_rate_tab.ts b/x-pack/test/functional/apps/infra/logs/log_entry_rate_tab.ts index b2b4b5bcfc0be..35aa6ec6ca4ae 100644 --- a/x-pack/test/functional/apps/infra/logs/log_entry_rate_tab.ts +++ b/x-pack/test/functional/apps/infra/logs/log_entry_rate_tab.ts @@ -9,16 +9,56 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -export default ({ getService }: FtrProviderContext) => { +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const PageObjects = getPageObjects(['security']); const logsUi = getService('logsUi'); const retry = getService('retry'); const esArchiver = getService('esArchiver'); + const security = getService('security'); describe('Log Entry Rate Tab', function () { this.tags('includeFirefox'); + const loginWithMLPrivileges = async (privileges: Record) => { + await security.role.create('global_logs_role', { + elasticsearch: { + cluster: ['all'], + indices: [{ names: ['*'], privileges: ['read', 'view_index_metadata'] }], + }, + kibana: [ + { + feature: { + logs: ['read'], + ...privileges, + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create('global_logs_read_user', { + password: 'global_logs_read_user-password', + roles: ['global_logs_role'], + full_name: 'logs test user', + }); + + await PageObjects.security.forceLogout(); + + await PageObjects.security.login('global_logs_read_user', 'global_logs_read_user-password', { + expectSpaceSelector: false, + }); + }; + + const logoutAndDeleteUser = async () => { + await PageObjects.security.forceLogout(); + await Promise.all([ + security.role.delete('global_logs_role'), + security.user.delete('global_logs_read_user'), + ]); + }; + describe('with a trial license', () => { - it('Shows no data page when indices do not exist', async () => { + it('shows no data page when indices do not exist', async () => { await logsUi.logEntryRatePage.navigateTo(); await retry.try(async () => { @@ -26,14 +66,42 @@ export default ({ getService }: FtrProviderContext) => { }); }); - it('shows setup page when indices exist', async () => { - await esArchiver.load('x-pack/test/functional/es_archives/infra/simple_logs'); - await logsUi.logEntryRatePage.navigateTo(); + describe('when indices exists', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + }); - await retry.try(async () => { - expect(await logsUi.logEntryRatePage.getSetupScreen()).to.be.ok(); + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + }); + + it('shows setup page when indices exist', async () => { + await logsUi.logEntryRatePage.navigateTo(); + + await retry.try(async () => { + expect(await logsUi.logEntryRatePage.getSetupScreen()).to.be.ok(); + }); + }); + + it('shows required ml read privileges prompt when the user has not any ml privileges', async () => { + await loginWithMLPrivileges({}); + await logsUi.logEntryRatePage.navigateTo(); + + await retry.try(async () => { + expect(await logsUi.logEntryRatePage.getNoMlReadPrivilegesPrompt()).to.be.ok(); + }); + await logoutAndDeleteUser(); + }); + + it('shows required ml all privileges prompt when the user has only ml read privileges', async () => { + await loginWithMLPrivileges({ ml: ['read'] }); + await logsUi.logEntryRatePage.navigateTo(); + + await retry.try(async () => { + expect(await logsUi.logEntryRatePage.getNoMlAllPrivilegesPrompt()).to.be.ok(); + }); + await logoutAndDeleteUser(); }); - await esArchiver.unload('x-pack/test/functional/es_archives/infra/simple_logs'); }); }); }); diff --git a/x-pack/test/functional/services/logs_ui/log_entry_categories.ts b/x-pack/test/functional/services/logs_ui/log_entry_categories.ts index 77098bd918ea6..d270b510bffbd 100644 --- a/x-pack/test/functional/services/logs_ui/log_entry_categories.ts +++ b/x-pack/test/functional/services/logs_ui/log_entry_categories.ts @@ -24,5 +24,13 @@ export function LogEntryCategoriesPageProvider({ getPageObjects, getService }: F async getSetupScreen(): Promise { return await testSubjects.find('logEntryCategoriesSetupPage'); }, + + getNoMlReadPrivilegesPrompt() { + return testSubjects.find('logsMissingMLReadPrivileges'); + }, + + getNoMlAllPrivilegesPrompt() { + return testSubjects.find('logsMissingMLAllPrivileges'); + }, }; } diff --git a/x-pack/test/functional/services/logs_ui/log_entry_rate.ts b/x-pack/test/functional/services/logs_ui/log_entry_rate.ts index f8a68f6c924e0..9b704db9eb021 100644 --- a/x-pack/test/functional/services/logs_ui/log_entry_rate.ts +++ b/x-pack/test/functional/services/logs_ui/log_entry_rate.ts @@ -29,6 +29,14 @@ export function LogEntryRatePageProvider({ getPageObjects, getService }: FtrProv return await testSubjects.find('noDataPage'); }, + getNoMlReadPrivilegesPrompt() { + return testSubjects.find('logsMissingMLReadPrivileges'); + }, + + getNoMlAllPrivilegesPrompt() { + return testSubjects.find('logsMissingMLAllPrivileges'); + }, + async startJobSetup() { await testSubjects.click('infraLogEntryRateSetupContentMlSetupButton'); }, diff --git a/x-pack/test/functional_execution_context/tests/browser.ts b/x-pack/test/functional_execution_context/tests/browser.ts index e1d7ba6a3b965..c7228528ee756 100644 --- a/x-pack/test/functional_execution_context/tests/browser.ts +++ b/x-pack/test/functional_execution_context/tests/browser.ts @@ -85,14 +85,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { type: 'application', name: 'discover', url: '/app/discover', - child: { - name: 'discover', - url: '/app/discover', - type: 'application', - page: 'app', - id: 'new', - description: 'fetch documents', - }, + page: 'app', + id: 'new', + description: 'fetch documents', }), }); }); @@ -105,20 +100,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { type: 'application', name: 'discover', url: '/app/discover', + page: 'app', + id: 'new', + description: 'fetch chart data and total hits', child: { - name: 'discover', - url: '/app/discover', - type: 'application', - page: 'app', - id: 'new', - description: 'fetch chart data and total hits', - child: { - type: 'lens', - name: 'lnsXY', - id: 'unifiedHistogramLensComponent', - description: 'Edit visualization', - url: '/app/lens#/edit_by_value', - }, + type: 'lens', + name: 'lnsXY', + id: 'unifiedHistogramLensComponent', + description: 'Edit visualization', + url: '/app/lens#/edit_by_value', }, }), }); @@ -185,9 +175,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('propagates to Elasticsearch via "x-opaque-id" header', async () => { await logContains({ description: 'execution context propagates to Elasticsearch via "x-opaque-id" header', - predicate: checkHttpRequestId( - 'dashboard:dashboards:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsXY:086ac2e9-dd16-4b45-92b8-1e43ff7e3f65' - ), + predicate: checkHttpRequestId('lens:lnsXY:086ac2e9-dd16-4b45-92b8-1e43ff7e3f65'), }); }); @@ -195,23 +183,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await logContains({ description: 'execution context propagates to Kibana logs', predicate: checkExecutionContextEntry({ - type: 'application', + type: 'dashboard', name: 'dashboards', url: '/app/dashboards', + page: 'app', + id: '7adfa750-4c81-11e8-b3d7-01146121b73d', + description: '[Flights] Global Flight Dashboard', child: { - name: 'dashboards', - url: '/app/dashboards', - type: 'dashboard', - page: 'app', - id: '7adfa750-4c81-11e8-b3d7-01146121b73d', - description: '[Flights] Global Flight Dashboard', - child: { - type: 'lens', - name: 'lnsXY', - id: '086ac2e9-dd16-4b45-92b8-1e43ff7e3f65', - description: '[Flights] Flight count', - url: '/app/lens#/edit_by_value', - }, + type: 'lens', + name: 'lnsXY', + id: '086ac2e9-dd16-4b45-92b8-1e43ff7e3f65', + description: '[Flights] Flight count', + url: '/app/lens#/edit_by_value', }, }), }); @@ -222,9 +205,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('propagates to Elasticsearch via "x-opaque-id" header', async () => { await logContains({ description: 'execution context propagates to Elasticsearch via "x-opaque-id" header', - predicate: checkHttpRequestId( - 'dashboard:dashboards:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsMetric:b766e3b8-4544-46ed-99e6-9ecc4847e2a2' - ), + predicate: checkHttpRequestId('lens:lnsMetric:b766e3b8-4544-46ed-99e6-9ecc4847e2a2'), }); }); @@ -232,23 +213,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await logContains({ description: 'execution context propagates to Kibana logs', predicate: checkExecutionContextEntry({ + type: 'dashboard', name: 'dashboards', url: '/app/dashboards', - type: 'application', + page: 'app', + id: '7adfa750-4c81-11e8-b3d7-01146121b73d', + description: '[Flights] Global Flight Dashboard', child: { - name: 'dashboards', - url: '/app/dashboards', - type: 'dashboard', - page: 'app', - id: '7adfa750-4c81-11e8-b3d7-01146121b73d', - description: '[Flights] Global Flight Dashboard', - child: { - type: 'lens', - name: 'lnsMetric', - id: 'b766e3b8-4544-46ed-99e6-9ecc4847e2a2', - description: '', - url: '/app/lens#/edit_by_value', - }, + type: 'lens', + name: 'lnsMetric', + id: 'b766e3b8-4544-46ed-99e6-9ecc4847e2a2', + description: '', + url: '/app/lens#/edit_by_value', }, }), }); @@ -260,7 +236,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await logContains({ description: 'execution context propagates to Elasticsearch via "x-opaque-id" header', predicate: checkHttpRequestId( - 'dashboard:dashboards:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsDatatable:fb86b32f-fb7a-45cf-9511-f366fef51bbd' + 'lens:lnsDatatable:fb86b32f-fb7a-45cf-9511-f366fef51bbd' ), }); }); @@ -269,23 +245,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await logContains({ description: 'execution context propagates to Kibana logs', predicate: checkExecutionContextEntry({ + type: 'dashboard', name: 'dashboards', url: '/app/dashboards', - type: 'application', + page: 'app', + id: '7adfa750-4c81-11e8-b3d7-01146121b73d', + description: '[Flights] Global Flight Dashboard', child: { - name: 'dashboards', - url: '/app/dashboards', - type: 'dashboard', - page: 'app', - id: '7adfa750-4c81-11e8-b3d7-01146121b73d', - description: '[Flights] Global Flight Dashboard', - child: { - type: 'lens', - name: 'lnsDatatable', - id: 'fb86b32f-fb7a-45cf-9511-f366fef51bbd', - description: 'Cities by delay, cancellation', - url: '/app/lens#/edit_by_value', - }, + type: 'lens', + name: 'lnsDatatable', + id: 'fb86b32f-fb7a-45cf-9511-f366fef51bbd', + description: 'Cities by delay, cancellation', + url: '/app/lens#/edit_by_value', }, }), }); @@ -296,9 +267,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('propagates to Elasticsearch via "x-opaque-id" header', async () => { await logContains({ description: 'execution context propagates to Elasticsearch via "x-opaque-id" header', - predicate: checkHttpRequestId( - 'dashboard:dashboards:7adfa750-4c81-11e8-b3d7-01146121b73d;lens:lnsPie:5d53db36-2d5a-4adc-af7b-cec4c1a294e0' - ), + predicate: checkHttpRequestId('lens:lnsPie:5d53db36-2d5a-4adc-af7b-cec4c1a294e0'), }); }); @@ -306,23 +275,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await logContains({ description: 'execution context propagates to Kibana logs', predicate: checkExecutionContextEntry({ + type: 'dashboard', name: 'dashboards', url: '/app/dashboards', - type: 'application', + page: 'app', + id: '7adfa750-4c81-11e8-b3d7-01146121b73d', + description: '[Flights] Global Flight Dashboard', child: { - name: 'dashboards', - url: '/app/dashboards', - type: 'dashboard', - page: 'app', - id: '7adfa750-4c81-11e8-b3d7-01146121b73d', - description: '[Flights] Global Flight Dashboard', - child: { - type: 'lens', - name: 'lnsPie', - id: '5d53db36-2d5a-4adc-af7b-cec4c1a294e0', - description: '[Flights] Delay Type', - url: '/app/lens#/edit_by_value', - }, + type: 'lens', + name: 'lnsPie', + id: '5d53db36-2d5a-4adc-af7b-cec4c1a294e0', + description: '[Flights] Delay Type', + url: '/app/lens#/edit_by_value', }, }), }); @@ -334,9 +298,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('propagates to Elasticsearch via "x-opaque-id" header', async () => { await logContains({ description: 'execution context propagates to Elasticsearch via "x-opaque-id" header', - predicate: checkHttpRequestId( - 'dashboard:dashboards:7adfa750-4c81-11e8-b3d7-01146121b73d;search:discover:571aaf70-4c88-11e8-b3d7-01146121b73d' - ), + predicate: checkHttpRequestId('search:discover:571aaf70-4c88-11e8-b3d7-01146121b73d'), }); }); @@ -344,23 +306,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await logContains({ description: 'execution context propagates to Kibana logs', predicate: checkExecutionContextEntry({ - type: 'application', + type: 'dashboard', name: 'dashboards', url: '/app/dashboards', + page: 'app', + id: '7adfa750-4c81-11e8-b3d7-01146121b73d', + description: '[Flights] Global Flight Dashboard', child: { - type: 'dashboard', - name: 'dashboards', - url: '/app/dashboards', - page: 'app', - id: '7adfa750-4c81-11e8-b3d7-01146121b73d', - description: '[Flights] Global Flight Dashboard', - child: { - type: 'search', - name: 'discover', - id: '571aaf70-4c88-11e8-b3d7-01146121b73d', - description: '[Flights] Flight Log', - url: '/app/discover#/view/571aaf70-4c88-11e8-b3d7-01146121b73d', - }, + type: 'search', + name: 'discover', + id: '571aaf70-4c88-11e8-b3d7-01146121b73d', + description: '[Flights] Flight Log', + url: '/app/discover#/view/571aaf70-4c88-11e8-b3d7-01146121b73d', }, }), }); diff --git a/x-pack/test/reporting_functional/reporting_and_security/management.ts b/x-pack/test/reporting_functional/reporting_and_security/management.ts index 570c1bbdda4c7..b1a6c107b9bb7 100644 --- a/x-pack/test/reporting_functional/reporting_and_security/management.ts +++ b/x-pack/test/reporting_functional/reporting_and_security/management.ts @@ -57,6 +57,7 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { }); // FLAKY: https://github.com/elastic/kibana/issues/195144 + // FLAKY: https://github.com/elastic/kibana/issues/194731 describe.skip('Download report', () => { // use archived reports to allow reporting_user to view report jobs they've created before('log in as reporting user', async () => { diff --git a/x-pack/test/security_api_integration/anonymous.config.ts b/x-pack/test/security_api_integration/anonymous.config.ts index a53584b547efd..396514f540d6d 100644 --- a/x-pack/test/security_api_integration/anonymous.config.ts +++ b/x-pack/test/security_api_integration/anonymous.config.ts @@ -5,9 +5,10 @@ * 2.0. */ -import { FtrConfigProviderContext } from '@kbn/test'; import { resolve } from 'path'; +import type { FtrConfigProviderContext } from '@kbn/test'; + export default async function ({ readConfigFile }: FtrConfigProviderContext) { const kibanaAPITestsConfig = await readConfigFile( require.resolve('@kbn/test-suites-src/api_integration/config') diff --git a/x-pack/test/security_api_integration/anonymous_es_anonymous.config.ts b/x-pack/test/security_api_integration/anonymous_es_anonymous.config.ts index 60691769729fa..78d49634bc472 100644 --- a/x-pack/test/security_api_integration/anonymous_es_anonymous.config.ts +++ b/x-pack/test/security_api_integration/anonymous_es_anonymous.config.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrConfigProviderContext } from '@kbn/test'; +import type { FtrConfigProviderContext } from '@kbn/test'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const anonymousAPITestsConfig = await readConfigFile(require.resolve('./anonymous.config.ts')); diff --git a/x-pack/test/security_api_integration/api_keys.config.ts b/x-pack/test/security_api_integration/api_keys.config.ts index a1ec0e428845a..23295e91a8741 100644 --- a/x-pack/test/security_api_integration/api_keys.config.ts +++ b/x-pack/test/security_api_integration/api_keys.config.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { FtrConfigProviderContext } from '@kbn/test'; +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/security_api_integration/audit.config.ts b/x-pack/test/security_api_integration/audit.config.ts index 052f241470bee..858aeedbebcdb 100644 --- a/x-pack/test/security_api_integration/audit.config.ts +++ b/x-pack/test/security_api_integration/audit.config.ts @@ -6,7 +6,8 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; + +import type { FtrConfigProviderContext } from '@kbn/test'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); diff --git a/x-pack/test/security_api_integration/chips.config.ts b/x-pack/test/security_api_integration/chips.config.ts index b91a4ebfcc3e9..2aedcc767b615 100644 --- a/x-pack/test/security_api_integration/chips.config.ts +++ b/x-pack/test/security_api_integration/chips.config.ts @@ -5,9 +5,10 @@ * 2.0. */ -import { FtrConfigProviderContext } from '@kbn/test'; import { resolve } from 'path'; +import type { FtrConfigProviderContext } from '@kbn/test'; + export default async function ({ readConfigFile }: FtrConfigProviderContext) { const kibanaAPITestsConfig = await readConfigFile( require.resolve('@kbn/test-suites-src/api_integration/config') diff --git a/x-pack/test/security_api_integration/ftr_provider_context.d.ts b/x-pack/test/security_api_integration/ftr_provider_context.d.ts index 647664d640466..b05bbc8f6318d 100644 --- a/x-pack/test/security_api_integration/ftr_provider_context.d.ts +++ b/x-pack/test/security_api_integration/ftr_provider_context.d.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { GenericFtrProviderContext } from '@kbn/test'; -import { services } from './services'; +import type { GenericFtrProviderContext } from '@kbn/test'; + +import type { services } from './services'; export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/security_api_integration/http_bearer.config.ts b/x-pack/test/security_api_integration/http_bearer.config.ts index 87f0b39cbb648..6a01822811d20 100644 --- a/x-pack/test/security_api_integration/http_bearer.config.ts +++ b/x-pack/test/security_api_integration/http_bearer.config.ts @@ -5,8 +5,10 @@ * 2.0. */ -import { FtrConfigProviderContext } from '@kbn/test'; import { resolve } from 'path'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/security_api_integration/http_no_auth_providers.config.ts b/x-pack/test/security_api_integration/http_no_auth_providers.config.ts index b46bbe3134dd4..5df4eefe1212a 100644 --- a/x-pack/test/security_api_integration/http_no_auth_providers.config.ts +++ b/x-pack/test/security_api_integration/http_no_auth_providers.config.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { FtrConfigProviderContext } from '@kbn/test'; +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/security_api_integration/kerberos.config.ts b/x-pack/test/security_api_integration/kerberos.config.ts index ba948efde4cd9..28bed3bfe25ee 100644 --- a/x-pack/test/security_api_integration/kerberos.config.ts +++ b/x-pack/test/security_api_integration/kerberos.config.ts @@ -6,7 +6,9 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/security_api_integration/kerberos_anonymous_access.config.ts b/x-pack/test/security_api_integration/kerberos_anonymous_access.config.ts index 355f0e90bcd91..d315ad0cff42e 100644 --- a/x-pack/test/security_api_integration/kerberos_anonymous_access.config.ts +++ b/x-pack/test/security_api_integration/kerberos_anonymous_access.config.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrConfigProviderContext } from '@kbn/test'; +import type { FtrConfigProviderContext } from '@kbn/test'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const kerberosAPITestsConfig = await readConfigFile(require.resolve('./kerberos.config.ts')); diff --git a/x-pack/test/security_api_integration/login_selector.config.ts b/x-pack/test/security_api_integration/login_selector.config.ts index 47371973028f0..9517cb2a45e8e 100644 --- a/x-pack/test/security_api_integration/login_selector.config.ts +++ b/x-pack/test/security_api_integration/login_selector.config.ts @@ -7,8 +7,9 @@ import { readFileSync } from 'fs'; import { resolve } from 'path'; + import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; -import { FtrConfigProviderContext } from '@kbn/test'; +import type { FtrConfigProviderContext } from '@kbn/test'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const kibanaAPITestsConfig = await readConfigFile( diff --git a/x-pack/test/security_api_integration/oidc.config.ts b/x-pack/test/security_api_integration/oidc.config.ts index 43cc5292ea5c2..28baa97f1dcc4 100644 --- a/x-pack/test/security_api_integration/oidc.config.ts +++ b/x-pack/test/security_api_integration/oidc.config.ts @@ -6,7 +6,9 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/security_api_integration/oidc.http2.config.ts b/x-pack/test/security_api_integration/oidc.http2.config.ts index 957813ceb046e..034e008132890 100644 --- a/x-pack/test/security_api_integration/oidc.http2.config.ts +++ b/x-pack/test/security_api_integration/oidc.http2.config.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { FtrConfigProviderContext } from '@kbn/test'; import { CA_CERT_PATH } from '@kbn/dev-utils'; +import type { FtrConfigProviderContext } from '@kbn/test'; import { configureHTTP2 } from '@kbn/test-suites-src/common/configure_http2'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/security_api_integration/oidc_implicit_flow.config.ts b/x-pack/test/security_api_integration/oidc_implicit_flow.config.ts index 3b9edcbec6826..94841661f6ed1 100644 --- a/x-pack/test/security_api_integration/oidc_implicit_flow.config.ts +++ b/x-pack/test/security_api_integration/oidc_implicit_flow.config.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrConfigProviderContext } from '@kbn/test'; +import type { FtrConfigProviderContext } from '@kbn/test'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const oidcAPITestsConfig = await readConfigFile(require.resolve('./oidc.config.ts')); diff --git a/x-pack/test/security_api_integration/packages/helpers/saml/saml_tools.ts b/x-pack/test/security_api_integration/packages/helpers/saml/saml_tools.ts index 255625082407b..b1dd8f851caf0 100644 --- a/x-pack/test/security_api_integration/packages/helpers/saml/saml_tools.ts +++ b/x-pack/test/security_api_integration/packages/helpers/saml/saml_tools.ts @@ -9,10 +9,11 @@ import crypto from 'crypto'; import fs from 'fs'; import { stringify } from 'query-string'; import url from 'url'; -import zlib from 'zlib'; import { promisify } from 'util'; -import { parseString } from 'xml2js'; import { SignedXml } from 'xml-crypto'; +import { parseString } from 'xml2js'; +import zlib from 'zlib'; + import { KBN_KEY_PATH } from '@kbn/dev-utils'; /** diff --git a/x-pack/test/security_api_integration/pki.config.ts b/x-pack/test/security_api_integration/pki.config.ts index 49c23c8d80b79..e10be72339984 100644 --- a/x-pack/test/security_api_integration/pki.config.ts +++ b/x-pack/test/security_api_integration/pki.config.ts @@ -6,8 +6,10 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; + import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/security_api_integration/plugins/audit_log/server/plugin.ts b/x-pack/test/security_api_integration/plugins/audit_log/server/plugin.ts index d7495dfd80f8d..647faccd0c0d0 100644 --- a/x-pack/test/security_api_integration/plugins/audit_log/server/plugin.ts +++ b/x-pack/test/security_api_integration/plugins/audit_log/server/plugin.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Plugin, CoreSetup } from '@kbn/core/server'; +import type { CoreSetup, Plugin } from '@kbn/core/server'; export class AuditTrailTestPlugin implements Plugin { public setup(core: CoreSetup) { diff --git a/x-pack/test/security_api_integration/plugins/oidc_provider/server/index.ts b/x-pack/test/security_api_integration/plugins/oidc_provider/server/index.ts index 82e460a6a4c27..b049f74d0a69f 100644 --- a/x-pack/test/security_api_integration/plugins/oidc_provider/server/index.ts +++ b/x-pack/test/security_api_integration/plugins/oidc_provider/server/index.ts @@ -5,7 +5,8 @@ * 2.0. */ -import type { PluginInitializer, Plugin } from '@kbn/core/server'; +import type { Plugin, PluginInitializer } from '@kbn/core/server'; + import { initRoutes } from './init_routes'; export const plugin: PluginInitializer = async (): Promise => ({ diff --git a/x-pack/test/security_api_integration/plugins/saml_provider/server/index.ts b/x-pack/test/security_api_integration/plugins/saml_provider/server/index.ts index ad297baf7246f..865240e32e9f0 100644 --- a/x-pack/test/security_api_integration/plugins/saml_provider/server/index.ts +++ b/x-pack/test/security_api_integration/plugins/saml_provider/server/index.ts @@ -5,8 +5,9 @@ * 2.0. */ -import type { PluginInitializer, Plugin } from '@kbn/core/server'; -import { CloudSetup } from '@kbn/cloud-plugin/server'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; +import type { Plugin, PluginInitializer } from '@kbn/core/server'; + import { initRoutes } from './init_routes'; export interface PluginSetupDependencies { diff --git a/x-pack/test/security_api_integration/plugins/saml_provider/server/init_routes.ts b/x-pack/test/security_api_integration/plugins/saml_provider/server/init_routes.ts index ea23e04201a61..f9e84caca0531 100644 --- a/x-pack/test/security_api_integration/plugins/saml_provider/server/init_routes.ts +++ b/x-pack/test/security_api_integration/plugins/saml_provider/server/init_routes.ts @@ -5,12 +5,13 @@ * 2.0. */ -import { CoreSetup, PluginInitializerContext } from '@kbn/core/server'; +import type { CoreSetup, PluginInitializerContext } from '@kbn/core/server'; import { - getSAMLResponse, getSAMLRequestId, + getSAMLResponse, } from '@kbn/security-api-integration-helpers/saml/saml_tools'; -import { PluginSetupDependencies } from '.'; + +import type { PluginSetupDependencies } from '.'; export function initRoutes( pluginContext: PluginInitializerContext, diff --git a/x-pack/test/security_api_integration/plugins/user_profiles_consumer/server/index.ts b/x-pack/test/security_api_integration/plugins/user_profiles_consumer/server/index.ts index 3922cc233cca9..3c598e3278da1 100644 --- a/x-pack/test/security_api_integration/plugins/user_profiles_consumer/server/index.ts +++ b/x-pack/test/security_api_integration/plugins/user_profiles_consumer/server/index.ts @@ -5,10 +5,11 @@ * 2.0. */ -import type { PluginInitializer, Plugin, CoreSetup } from '@kbn/core/server'; -import { FeaturesPluginSetup, FeaturesPluginStart } from '@kbn/features-plugin/server'; -import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; -import { SpacesPluginSetup, SpacesPluginStart } from '@kbn/spaces-plugin/server'; +import type { CoreSetup, Plugin, PluginInitializer } from '@kbn/core/server'; +import type { FeaturesPluginSetup, FeaturesPluginStart } from '@kbn/features-plugin/server'; +import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; +import type { SpacesPluginSetup, SpacesPluginStart } from '@kbn/spaces-plugin/server'; + import { initRoutes } from './init_routes'; export interface PluginSetupDependencies { diff --git a/x-pack/test/security_api_integration/plugins/user_profiles_consumer/server/init_routes.ts b/x-pack/test/security_api_integration/plugins/user_profiles_consumer/server/init_routes.ts index 091e50ff17350..d7144dd69a1df 100644 --- a/x-pack/test/security_api_integration/plugins/user_profiles_consumer/server/init_routes.ts +++ b/x-pack/test/security_api_integration/plugins/user_profiles_consumer/server/init_routes.ts @@ -5,9 +5,10 @@ * 2.0. */ -import { CoreSetup } from '@kbn/core/server'; import { schema } from '@kbn/config-schema'; -import { PluginStartDependencies } from '.'; +import type { CoreSetup } from '@kbn/core/server'; + +import type { PluginStartDependencies } from '.'; export function initRoutes(core: CoreSetup) { const router = core.http.createRouter(); diff --git a/x-pack/test/security_api_integration/saml.config.ts b/x-pack/test/security_api_integration/saml.config.ts index 3cd8b55f4117b..1168fe9ed196b 100644 --- a/x-pack/test/security_api_integration/saml.config.ts +++ b/x-pack/test/security_api_integration/saml.config.ts @@ -6,7 +6,9 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/security_api_integration/saml.http2.config.ts b/x-pack/test/security_api_integration/saml.http2.config.ts index 0d063188efe9c..ddcd8ac7da446 100644 --- a/x-pack/test/security_api_integration/saml.http2.config.ts +++ b/x-pack/test/security_api_integration/saml.http2.config.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { FtrConfigProviderContext } from '@kbn/test'; import { CA_CERT_PATH } from '@kbn/dev-utils'; +import type { FtrConfigProviderContext } from '@kbn/test'; import { configureHTTP2 } from '@kbn/test-suites-src/common/configure_http2'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/security_api_integration/saml_cloud.config.ts b/x-pack/test/security_api_integration/saml_cloud.config.ts index ce392b8a092c1..86450379a4364 100644 --- a/x-pack/test/security_api_integration/saml_cloud.config.ts +++ b/x-pack/test/security_api_integration/saml_cloud.config.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { FtrConfigProviderContext } from '@kbn/test'; +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/security_api_integration/services.ts b/x-pack/test/security_api_integration/services.ts index c2298b21b55e6..6b12d42f6e3d0 100644 --- a/x-pack/test/security_api_integration/services.ts +++ b/x-pack/test/security_api_integration/services.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { services as commonServices } from '../common/services'; import { services as apiIntegrationServices } from '../api_integration/services'; +import { services as commonServices } from '../common/services'; export const services = { ...commonServices, diff --git a/x-pack/test/security_api_integration/session_concurrent_limit.config.ts b/x-pack/test/security_api_integration/session_concurrent_limit.config.ts index 63da7220f3959..ff3e9f590a749 100644 --- a/x-pack/test/security_api_integration/session_concurrent_limit.config.ts +++ b/x-pack/test/security_api_integration/session_concurrent_limit.config.ts @@ -6,7 +6,9 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; // the default export of config files must be a config provider diff --git a/x-pack/test/security_api_integration/session_idle.config.ts b/x-pack/test/security_api_integration/session_idle.config.ts index a4943fb5388f4..9509d919c8ddd 100644 --- a/x-pack/test/security_api_integration/session_idle.config.ts +++ b/x-pack/test/security_api_integration/session_idle.config.ts @@ -6,7 +6,9 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; // the default export of config files must be a config provider diff --git a/x-pack/test/security_api_integration/session_invalidate.config.ts b/x-pack/test/security_api_integration/session_invalidate.config.ts index 2e0dd37f054e0..33be8e8f8f0d2 100644 --- a/x-pack/test/security_api_integration/session_invalidate.config.ts +++ b/x-pack/test/security_api_integration/session_invalidate.config.ts @@ -6,7 +6,9 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; // the default export of config files must be a config provider diff --git a/x-pack/test/security_api_integration/session_lifespan.config.ts b/x-pack/test/security_api_integration/session_lifespan.config.ts index 41e723a736809..9b37f45327c5c 100644 --- a/x-pack/test/security_api_integration/session_lifespan.config.ts +++ b/x-pack/test/security_api_integration/session_lifespan.config.ts @@ -6,7 +6,9 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; // the default export of config files must be a config provider diff --git a/x-pack/test/security_api_integration/tests/anonymous/capabilities.ts b/x-pack/test/security_api_integration/tests/anonymous/capabilities.ts index baa9e9ff419a4..f4f43b070f0d5 100644 --- a/x-pack/test/security_api_integration/tests/anonymous/capabilities.ts +++ b/x-pack/test/security_api_integration/tests/anonymous/capabilities.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/security_api_integration/tests/anonymous/index.ts b/x-pack/test/security_api_integration/tests/anonymous/index.ts index 0f976589483a8..59bf0f1d75d34 100644 --- a/x-pack/test/security_api_integration/tests/anonymous/index.ts +++ b/x-pack/test/security_api_integration/tests/anonymous/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - Anonymous access', function () { diff --git a/x-pack/test/security_api_integration/tests/anonymous/login.ts b/x-pack/test/security_api_integration/tests/anonymous/login.ts index b97067ba04e7d..3495bd4d71dac 100644 --- a/x-pack/test/security_api_integration/tests/anonymous/login.ts +++ b/x-pack/test/security_api_integration/tests/anonymous/login.ts @@ -5,11 +5,14 @@ * 2.0. */ +import { resolve } from 'path'; +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; + import expect from '@kbn/expect'; -import { parse as parseCookie, Cookie } from 'tough-cookie'; import { adminTestUser } from '@kbn/test'; -import { resolve } from 'path'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; import { FileWrapper } from '../audit/file_wrapper'; export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test/security_api_integration/tests/api_keys/grant_api_key.ts b/x-pack/test/security_api_integration/tests/api_keys/grant_api_key.ts index bc559f47ed21d..c6e69d9beff1c 100644 --- a/x-pack/test/security_api_integration/tests/api_keys/grant_api_key.ts +++ b/x-pack/test/security_api_integration/tests/api_keys/grant_api_key.ts @@ -7,7 +7,8 @@ import expect from '@kbn/expect'; import { adminTestUser } from '@kbn/test'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/api_keys/has_active_key.ts b/x-pack/test/security_api_integration/tests/api_keys/has_active_key.ts index 6b14df9ad489e..6a6c98271a888 100644 --- a/x-pack/test/security_api_integration/tests/api_keys/has_active_key.ts +++ b/x-pack/test/security_api_integration/tests/api_keys/has_active_key.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; + import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test/security_api_integration/tests/api_keys/index.ts b/x-pack/test/security_api_integration/tests/api_keys/index.ts index a36a76c0c9566..11debcafe7471 100644 --- a/x-pack/test/security_api_integration/tests/api_keys/index.ts +++ b/x-pack/test/security_api_integration/tests/api_keys/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - Api Keys', function () { diff --git a/x-pack/test/security_api_integration/tests/audit/audit_log.ts b/x-pack/test/security_api_integration/tests/audit/audit_log.ts index e8c5d03bfc562..083ae82d4e406 100644 --- a/x-pack/test/security_api_integration/tests/audit/audit_log.ts +++ b/x-pack/test/security_api_integration/tests/audit/audit_log.ts @@ -6,9 +6,11 @@ */ import Path from 'path'; + import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; + import { FileWrapper } from './file_wrapper'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/security_api_integration/tests/audit/file_wrapper.ts b/x-pack/test/security_api_integration/tests/audit/file_wrapper.ts index b542ef0f8e354..2c26b07d8bd1a 100644 --- a/x-pack/test/security_api_integration/tests/audit/file_wrapper.ts +++ b/x-pack/test/security_api_integration/tests/audit/file_wrapper.ts @@ -6,6 +6,7 @@ */ import Fs from 'fs'; + import type { RetryService } from '@kbn/ftr-common-functional-services'; export class FileWrapper { diff --git a/x-pack/test/security_api_integration/tests/audit/index.ts b/x-pack/test/security_api_integration/tests/audit/index.ts index 96b2ceb5ae3a7..aeb67988e56ff 100644 --- a/x-pack/test/security_api_integration/tests/audit/index.ts +++ b/x-pack/test/security_api_integration/tests/audit/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - Audit Log', function () { diff --git a/x-pack/test/security_api_integration/tests/chips/chips_cookie.ts b/x-pack/test/security_api_integration/tests/chips/chips_cookie.ts index 9a6a811578664..753bae24ae9c7 100644 --- a/x-pack/test/security_api_integration/tests/chips/chips_cookie.ts +++ b/x-pack/test/security_api_integration/tests/chips/chips_cookie.ts @@ -13,9 +13,11 @@ */ import { parse as parseCookie } from 'tough-cookie'; -import { adminTestUser } from '@kbn/test'; + import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { adminTestUser } from '@kbn/test'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/chips/index.ts b/x-pack/test/security_api_integration/tests/chips/index.ts index 2379a5feae5d8..f391d6d2bc496 100644 --- a/x-pack/test/security_api_integration/tests/chips/index.ts +++ b/x-pack/test/security_api_integration/tests/chips/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - CHIPS support', function () { diff --git a/x-pack/test/security_api_integration/tests/http_bearer/access_token.ts b/x-pack/test/security_api_integration/tests/http_bearer/access_token.ts index 3e94adfc3ece8..26a70bb286a1f 100644 --- a/x-pack/test/security_api_integration/tests/http_bearer/access_token.ts +++ b/x-pack/test/security_api_integration/tests/http_bearer/access_token.ts @@ -7,7 +7,8 @@ import expect from '@kbn/expect'; import { adminTestUser } from '@kbn/test'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/http_bearer/index.ts b/x-pack/test/security_api_integration/tests/http_bearer/index.ts index 66619192bca48..950dcabc6b88a 100644 --- a/x-pack/test/security_api_integration/tests/http_bearer/index.ts +++ b/x-pack/test/security_api_integration/tests/http_bearer/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - HTTP Bearer', function () { diff --git a/x-pack/test/security_api_integration/tests/http_bearer/jwt.ts b/x-pack/test/security_api_integration/tests/http_bearer/jwt.ts index 4806bc410cd84..2ccfdeec77cb5 100644 --- a/x-pack/test/security_api_integration/tests/http_bearer/jwt.ts +++ b/x-pack/test/security_api_integration/tests/http_bearer/jwt.ts @@ -6,9 +6,10 @@ */ import expect from '@kbn/expect'; -import { AuthenticatedUser } from '@kbn/security-plugin-types-common'; +import type { AuthenticatedUser } from '@kbn/security-plugin-types-common'; import { adminTestUser } from '@kbn/test'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/http_no_auth_providers/authentication.ts b/x-pack/test/security_api_integration/tests/http_no_auth_providers/authentication.ts index 42951f8c3fed3..c9b0c601316ba 100644 --- a/x-pack/test/security_api_integration/tests/http_no_auth_providers/authentication.ts +++ b/x-pack/test/security_api_integration/tests/http_no_auth_providers/authentication.ts @@ -7,7 +7,8 @@ import expect from '@kbn/expect'; import { adminTestUser } from '@kbn/test'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/http_no_auth_providers/index.ts b/x-pack/test/security_api_integration/tests/http_no_auth_providers/index.ts index 23096b2449c9f..1ed90a8c24729 100644 --- a/x-pack/test/security_api_integration/tests/http_no_auth_providers/index.ts +++ b/x-pack/test/security_api_integration/tests/http_no_auth_providers/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - HTTP no authentication providers are enabled', function () { diff --git a/x-pack/test/security_api_integration/tests/kerberos/index.ts b/x-pack/test/security_api_integration/tests/kerberos/index.ts index 828ce7220458f..dc101b5de7c8b 100644 --- a/x-pack/test/security_api_integration/tests/kerberos/index.ts +++ b/x-pack/test/security_api_integration/tests/kerberos/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - Kerberos', function () { diff --git a/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts index 82ec3b062405c..edfc037c936fe 100644 --- a/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts +++ b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts @@ -5,17 +5,20 @@ * 2.0. */ -import expect from '@kbn/expect'; import { expect as jestExpect } from 'expect'; -import { parse as parseCookie, Cookie } from 'tough-cookie'; -import { setTimeout as setTimeoutAsync } from 'timers/promises'; -import { adminTestUser } from '@kbn/test'; import { resolve } from 'path'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; + +import expect from '@kbn/expect'; import { getMutualAuthenticationResponseToken, getSPNEGOToken, } from '@kbn/security-api-integration-helpers/kerberos/kerberos_tools'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { adminTestUser } from '@kbn/test'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; import { FileWrapper } from '../audit/file_wrapper'; export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts index 1f2dc1ab43775..6536c9a08063d 100644 --- a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts +++ b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts @@ -5,22 +5,25 @@ * 2.0. */ -import { parse as parseCookie, Cookie } from 'tough-cookie'; import { readFileSync } from 'fs'; +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; import url from 'url'; + import { CA_CERT_PATH } from '@kbn/dev-utils'; import expect from '@kbn/expect'; -import type { AuthenticationProvider } from '@kbn/security-plugin/common'; -import { getStateAndNonce } from '@kbn/security-api-integration-helpers/oidc/oidc_tools'; import { getMutualAuthenticationResponseToken, getSPNEGOToken, } from '@kbn/security-api-integration-helpers/kerberos/kerberos_tools'; +import { getStateAndNonce } from '@kbn/security-api-integration-helpers/oidc/oidc_tools'; import { getSAMLRequestId, getSAMLResponse, } from '@kbn/security-api-integration-helpers/saml/saml_tools'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { AuthenticationProvider } from '@kbn/security-plugin/common'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const randomness = getService('randomness'); diff --git a/x-pack/test/security_api_integration/tests/login_selector/index.ts b/x-pack/test/security_api_integration/tests/login_selector/index.ts index e3698340d3967..65fdef8e465e3 100644 --- a/x-pack/test/security_api_integration/tests/login_selector/index.ts +++ b/x-pack/test/security_api_integration/tests/login_selector/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - Login Selector', function () { diff --git a/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/index.ts b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/index.ts index 2c8edc1569bd2..e4f9c6805c3de 100644 --- a/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/index.ts +++ b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../../ftr_provider_context'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - OIDC (Authorization Code Flow)', function () { diff --git a/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts index 4c20b091f6e11..c14379ce753af 100644 --- a/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts +++ b/x-pack/test/security_api_integration/tests/oidc/authorization_code_flow/oidc_auth.ts @@ -5,14 +5,17 @@ * 2.0. */ -import expect from '@kbn/expect'; -import { parse as parseCookie, Cookie } from 'tough-cookie'; -import url from 'url'; -import { setTimeout as setTimeoutAsync } from 'timers/promises'; -import { adminTestUser } from '@kbn/test'; import { resolve } from 'path'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; +import url from 'url'; + +import expect from '@kbn/expect'; import { getStateAndNonce } from '@kbn/security-api-integration-helpers/oidc/oidc_tools'; -import { FtrProviderContext } from '../../../ftr_provider_context'; +import { adminTestUser } from '@kbn/test'; + +import type { FtrProviderContext } from '../../../ftr_provider_context'; import { FileWrapper } from '../../audit/file_wrapper'; export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test/security_api_integration/tests/oidc/implicit_flow/index.ts b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/index.ts index 7479ba8e7bd81..6c3215f0f34c1 100644 --- a/x-pack/test/security_api_integration/tests/oidc/implicit_flow/index.ts +++ b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../../ftr_provider_context'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - OIDC (Implicit Flow)', function () { diff --git a/x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts index af5a78e7d4450..3a72b2ebe5fb7 100644 --- a/x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts +++ b/x-pack/test/security_api_integration/tests/oidc/implicit_flow/oidc_auth.ts @@ -5,15 +5,18 @@ * 2.0. */ -import expect from '@kbn/expect'; import { JSDOM } from 'jsdom'; -import { parse as parseCookie, Cookie } from 'tough-cookie'; +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; import { format as formatURL } from 'url'; + +import expect from '@kbn/expect'; import { createTokens, getStateAndNonce, } from '@kbn/security-api-integration-helpers/oidc/oidc_tools'; -import { FtrProviderContext } from '../../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/pki/index.ts b/x-pack/test/security_api_integration/tests/pki/index.ts index 9926f16619898..9d428f969d12e 100644 --- a/x-pack/test/security_api_integration/tests/pki/index.ts +++ b/x-pack/test/security_api_integration/tests/pki/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - PKI', function () { diff --git a/x-pack/test/security_api_integration/tests/pki/pki_auth.ts b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts index 37fae88942f34..bd77e029c1529 100644 --- a/x-pack/test/security_api_integration/tests/pki/pki_auth.ts +++ b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts @@ -5,15 +5,18 @@ * 2.0. */ -import expect from '@kbn/expect'; import { expect as jestExpect } from 'expect'; -import { parse as parseCookie, Cookie } from 'tough-cookie'; -import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { readFileSync } from 'fs'; import { resolve } from 'path'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; + import { CA_CERT_PATH } from '@kbn/dev-utils'; +import expect from '@kbn/expect'; import { adminTestUser } from '@kbn/test'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; import { FileWrapper } from '../audit/file_wrapper'; const CA_CERT = readFileSync(CA_CERT_PATH); diff --git a/x-pack/test/security_api_integration/tests/saml/index.ts b/x-pack/test/security_api_integration/tests/saml/index.ts index 3597f1a6104ec..bacf31dd3a81a 100644 --- a/x-pack/test/security_api_integration/tests/saml/index.ts +++ b/x-pack/test/security_api_integration/tests/saml/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - SAML', function () { diff --git a/x-pack/test/security_api_integration/tests/saml/saml_login.ts b/x-pack/test/security_api_integration/tests/saml/saml_login.ts index e80c19c40526d..71994bc5512ca 100644 --- a/x-pack/test/security_api_integration/tests/saml/saml_login.ts +++ b/x-pack/test/security_api_integration/tests/saml/saml_login.ts @@ -5,19 +5,22 @@ * 2.0. */ -import { stringify } from 'query-string'; -import url from 'url'; import { resolve } from 'path'; +import { stringify } from 'query-string'; import { setTimeout as setTimeoutAsync } from 'timers/promises'; +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; +import url from 'url'; + import expect from '@kbn/expect'; -import { parse as parseCookie, Cookie } from 'tough-cookie'; -import { adminTestUser } from '@kbn/test'; import { getLogoutRequest, getSAMLRequestId, getSAMLResponse, } from '@kbn/security-api-integration-helpers/saml/saml_tools'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { adminTestUser } from '@kbn/test'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; import { FileWrapper } from '../audit/file_wrapper'; export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test/security_api_integration/tests/saml_cloud/index.ts b/x-pack/test/security_api_integration/tests/saml_cloud/index.ts index a2b39ad247151..61b4a166f666c 100644 --- a/x-pack/test/security_api_integration/tests/saml_cloud/index.ts +++ b/x-pack/test/security_api_integration/tests/saml_cloud/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - Cloud SAML', function () { diff --git a/x-pack/test/security_api_integration/tests/saml_cloud/saml_login.ts b/x-pack/test/security_api_integration/tests/saml_cloud/saml_login.ts index 7c585fcf4ffae..1282654506300 100644 --- a/x-pack/test/security_api_integration/tests/saml_cloud/saml_login.ts +++ b/x-pack/test/security_api_integration/tests/saml_cloud/saml_login.ts @@ -5,10 +5,13 @@ * 2.0. */ +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; + import expect from '@kbn/expect'; -import { parse as parseCookie, Cookie } from 'tough-cookie'; import { getSAMLResponse } from '@kbn/security-api-integration-helpers/saml/saml_tools'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const randomness = getService('randomness'); diff --git a/x-pack/test/security_api_integration/tests/session_concurrent_limit/cleanup.ts b/x-pack/test/security_api_integration/tests/session_concurrent_limit/cleanup.ts index 6aa782ad260df..cac4f3b678149 100644 --- a/x-pack/test/security_api_integration/tests/session_concurrent_limit/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_concurrent_limit/cleanup.ts @@ -5,23 +5,26 @@ * 2.0. */ -import { parse as parseCookie, Cookie } from 'tough-cookie'; -import { setTimeout as setTimeoutAsync } from 'timers/promises'; -import expect from '@kbn/expect'; -import { adminTestUser } from '@kbn/test'; -import type { AuthenticationProvider } from '@kbn/security-plugin/common'; -import { +import type { AggregateName, AggregationsMultiTermsAggregate, AggregationsMultiTermsBucket, AggregationsTopHitsAggregate, SearchTotalHits, } from '@elastic/elasticsearch/lib/api/types'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; + +import expect from '@kbn/expect'; import { getSAMLRequestId, getSAMLResponse, } from '@kbn/security-api-integration-helpers/saml/saml_tools'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { AuthenticationProvider } from '@kbn/security-plugin/common'; +import { adminTestUser } from '@kbn/test'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/session_concurrent_limit/global_limit.ts b/x-pack/test/security_api_integration/tests/session_concurrent_limit/global_limit.ts index df120e033377c..b1bb30e1a6cb6 100644 --- a/x-pack/test/security_api_integration/tests/session_concurrent_limit/global_limit.ts +++ b/x-pack/test/security_api_integration/tests/session_concurrent_limit/global_limit.ts @@ -5,15 +5,18 @@ * 2.0. */ -import { parse as parseCookie, Cookie } from 'tough-cookie'; +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; + import expect from '@kbn/expect'; -import { adminTestUser } from '@kbn/test'; -import type { AuthenticationProvider } from '@kbn/security-plugin/common'; import { getSAMLRequestId, getSAMLResponse, } from '@kbn/security-api-integration-helpers/saml/saml_tools'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { AuthenticationProvider } from '@kbn/security-plugin/common'; +import { adminTestUser } from '@kbn/test'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/session_concurrent_limit/index.ts b/x-pack/test/security_api_integration/tests/session_concurrent_limit/index.ts index 61463eaccb09b..ce4b0f61a708f 100644 --- a/x-pack/test/security_api_integration/tests/session_concurrent_limit/index.ts +++ b/x-pack/test/security_api_integration/tests/session_concurrent_limit/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - Session Concurrent Limit', function () { diff --git a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts index 4a74e3938467e..9c0f92b2c0182 100644 --- a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts @@ -5,16 +5,19 @@ * 2.0. */ -import { parse as parseCookie, Cookie } from 'tough-cookie'; import { setTimeout as setTimeoutAsync } from 'timers/promises'; +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; + import expect from '@kbn/expect'; -import { adminTestUser } from '@kbn/test'; -import type { AuthenticationProvider } from '@kbn/security-plugin/common'; import { getSAMLRequestId, getSAMLResponse, } from '@kbn/security-api-integration-helpers/saml/saml_tools'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { AuthenticationProvider } from '@kbn/security-plugin/common'; +import { adminTestUser } from '@kbn/test'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/session_idle/expired.ts b/x-pack/test/security_api_integration/tests/session_idle/expired.ts index 14c7370068634..f5eb553a462ff 100644 --- a/x-pack/test/security_api_integration/tests/session_idle/expired.ts +++ b/x-pack/test/security_api_integration/tests/session_idle/expired.ts @@ -5,12 +5,14 @@ * 2.0. */ -import { parse as parseCookie } from 'tough-cookie'; import { setTimeout as setTimeoutAsync } from 'timers/promises'; +import { parse as parseCookie } from 'tough-cookie'; + import expect from '@kbn/expect'; -import { adminTestUser } from '@kbn/test'; import { SESSION_ERROR_REASON_HEADER } from '@kbn/security-plugin/common/constants'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { adminTestUser } from '@kbn/test'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/session_idle/extension.ts b/x-pack/test/security_api_integration/tests/session_idle/extension.ts index 83a3d1a534919..71e1c370387de 100644 --- a/x-pack/test/security_api_integration/tests/session_idle/extension.ts +++ b/x-pack/test/security_api_integration/tests/session_idle/extension.ts @@ -5,10 +5,13 @@ * 2.0. */ -import { parse as parseCookie, Cookie } from 'tough-cookie'; -import expect from '@kbn/expect'; import { setTimeout as setTimeoutAsync } from 'timers/promises'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; + +import expect from '@kbn/expect'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/session_idle/index.ts b/x-pack/test/security_api_integration/tests/session_idle/index.ts index 14015874ddb61..d23861046ba59 100644 --- a/x-pack/test/security_api_integration/tests/session_idle/index.ts +++ b/x-pack/test/security_api_integration/tests/session_idle/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - Session Idle', function () { diff --git a/x-pack/test/security_api_integration/tests/session_invalidate/index.ts b/x-pack/test/security_api_integration/tests/session_invalidate/index.ts index dcfb3d7fc5259..e244b08d03719 100644 --- a/x-pack/test/security_api_integration/tests/session_invalidate/index.ts +++ b/x-pack/test/security_api_integration/tests/session_invalidate/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - Session Invalidate', function () { diff --git a/x-pack/test/security_api_integration/tests/session_invalidate/invalidate.ts b/x-pack/test/security_api_integration/tests/session_invalidate/invalidate.ts index 7c4d344a481d0..184b46a7d13c2 100644 --- a/x-pack/test/security_api_integration/tests/session_invalidate/invalidate.ts +++ b/x-pack/test/security_api_integration/tests/session_invalidate/invalidate.ts @@ -5,15 +5,18 @@ * 2.0. */ -import { parse as parseCookie, Cookie } from 'tough-cookie'; +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; + import expect from '@kbn/expect'; -import { adminTestUser } from '@kbn/test'; -import type { AuthenticationProvider } from '@kbn/security-plugin/common'; import { getSAMLRequestId, getSAMLResponse, } from '@kbn/security-api-integration-helpers/saml/saml_tools'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { AuthenticationProvider } from '@kbn/security-plugin/common'; +import { adminTestUser } from '@kbn/test'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts index 5f7efaefd6242..77de7d5915a45 100644 --- a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts @@ -5,16 +5,19 @@ * 2.0. */ -import { parse as parseCookie, Cookie } from 'tough-cookie'; import { setTimeout as setTimeoutAsync } from 'timers/promises'; +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; + import expect from '@kbn/expect'; -import { adminTestUser } from '@kbn/test'; -import type { AuthenticationProvider } from '@kbn/security-plugin/common'; import { getSAMLRequestId, getSAMLResponse, } from '@kbn/security-api-integration-helpers/saml/saml_tools'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { AuthenticationProvider } from '@kbn/security-plugin/common'; +import { adminTestUser } from '@kbn/test'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/session_lifespan/index.ts b/x-pack/test/security_api_integration/tests/session_lifespan/index.ts index e297805b4ab3c..e8f96c7f83f98 100644 --- a/x-pack/test/security_api_integration/tests/session_lifespan/index.ts +++ b/x-pack/test/security_api_integration/tests/session_lifespan/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - Session Lifespan', function () { diff --git a/x-pack/test/security_api_integration/tests/token/audit.ts b/x-pack/test/security_api_integration/tests/token/audit.ts index 0c97fb9c3cdc4..7bd32b733b175 100644 --- a/x-pack/test/security_api_integration/tests/token/audit.ts +++ b/x-pack/test/security_api_integration/tests/token/audit.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { parse as parseCookie } from 'tough-cookie'; import { resolve } from 'path'; +import { parse as parseCookie } from 'tough-cookie'; + import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; import { FileWrapper } from '../audit/file_wrapper'; export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test/security_api_integration/tests/token/header.ts b/x-pack/test/security_api_integration/tests/token/header.ts index 74707aee68931..304d2f4b0e4dd 100644 --- a/x-pack/test/security_api_integration/tests/token/header.ts +++ b/x-pack/test/security_api_integration/tests/token/header.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/token/index.ts b/x-pack/test/security_api_integration/tests/token/index.ts index e38f5148d644d..d4ed1589370b4 100644 --- a/x-pack/test/security_api_integration/tests/token/index.ts +++ b/x-pack/test/security_api_integration/tests/token/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - Token', function () { diff --git a/x-pack/test/security_api_integration/tests/token/login.ts b/x-pack/test/security_api_integration/tests/token/login.ts index 25e7bb3251687..a05e78c1c1309 100644 --- a/x-pack/test/security_api_integration/tests/token/login.ts +++ b/x-pack/test/security_api_integration/tests/token/login.ts @@ -6,7 +6,8 @@ */ import { parse as parseCookie } from 'tough-cookie'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/token/logout.ts b/x-pack/test/security_api_integration/tests/token/logout.ts index 1a2385e434ca4..5c85f22964089 100644 --- a/x-pack/test/security_api_integration/tests/token/logout.ts +++ b/x-pack/test/security_api_integration/tests/token/logout.ts @@ -6,7 +6,8 @@ */ import { parse as parseCookie } from 'tough-cookie'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/token/session.ts b/x-pack/test/security_api_integration/tests/token/session.ts index 3e8d44ce247cc..1e90f278ea594 100644 --- a/x-pack/test/security_api_integration/tests/token/session.ts +++ b/x-pack/test/security_api_integration/tests/token/session.ts @@ -5,9 +5,12 @@ * 2.0. */ -import { parse as parseCookie, Cookie } from 'tough-cookie'; +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; + import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/x-pack/test/security_api_integration/tests/user_profiles/bulk_get.ts b/x-pack/test/security_api_integration/tests/user_profiles/bulk_get.ts index 8955a06261848..7c5accb39937c 100644 --- a/x-pack/test/security_api_integration/tests/user_profiles/bulk_get.ts +++ b/x-pack/test/security_api_integration/tests/user_profiles/bulk_get.ts @@ -5,9 +5,12 @@ * 2.0. */ +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; + import expect from '@kbn/expect'; -import { parse as parseCookie, Cookie } from 'tough-cookie'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/security_api_integration/tests/user_profiles/get_current.ts b/x-pack/test/security_api_integration/tests/user_profiles/get_current.ts index 44896f9792247..ec41bc3d80648 100644 --- a/x-pack/test/security_api_integration/tests/user_profiles/get_current.ts +++ b/x-pack/test/security_api_integration/tests/user_profiles/get_current.ts @@ -5,9 +5,10 @@ * 2.0. */ -import { parse as parseCookie } from 'tough-cookie'; import { expect } from 'expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { parse as parseCookie } from 'tough-cookie'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); diff --git a/x-pack/test/security_api_integration/tests/user_profiles/index.ts b/x-pack/test/security_api_integration/tests/user_profiles/index.ts index ab91926127f41..19197cfebbbcb 100644 --- a/x-pack/test/security_api_integration/tests/user_profiles/index.ts +++ b/x-pack/test/security_api_integration/tests/user_profiles/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security APIs - User Profiles', function () { diff --git a/x-pack/test/security_api_integration/tests/user_profiles/suggest.ts b/x-pack/test/security_api_integration/tests/user_profiles/suggest.ts index cf58f1b35d3b5..26b60abfc04d3 100644 --- a/x-pack/test/security_api_integration/tests/user_profiles/suggest.ts +++ b/x-pack/test/security_api_integration/tests/user_profiles/suggest.ts @@ -5,9 +5,12 @@ * 2.0. */ +import type { Cookie } from 'tough-cookie'; +import { parse as parseCookie } from 'tough-cookie'; + import expect from '@kbn/expect'; -import { parse as parseCookie, Cookie } from 'tough-cookie'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/security_api_integration/token.config.ts b/x-pack/test/security_api_integration/token.config.ts index 9021ab8dc2a58..5eefa10dc7621 100644 --- a/x-pack/test/security_api_integration/token.config.ts +++ b/x-pack/test/security_api_integration/token.config.ts @@ -5,8 +5,10 @@ * 2.0. */ -import { FtrConfigProviderContext } from '@kbn/test'; import { resolve } from 'path'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/security_api_integration/user_profiles.config.ts b/x-pack/test/security_api_integration/user_profiles.config.ts index 43d25920b2fd9..31b548e2dc3e6 100644 --- a/x-pack/test/security_api_integration/user_profiles.config.ts +++ b/x-pack/test/security_api_integration/user_profiles.config.ts @@ -6,7 +6,9 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/security_functional/expired_session.config.ts b/x-pack/test/security_functional/expired_session.config.ts index 82dd1cb36caf2..b02a96dd41360 100644 --- a/x-pack/test/security_functional/expired_session.config.ts +++ b/x-pack/test/security_functional/expired_session.config.ts @@ -6,9 +6,11 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; -import { services } from '../functional/services'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { pageObjects } from '../functional/page_objects'; +import { services } from '../functional/services'; // the default export of config files must be a config provider // that returns an object with the projects config values diff --git a/x-pack/test/security_functional/ftr_provider_context.d.ts b/x-pack/test/security_functional/ftr_provider_context.d.ts index 66d4e37b795ca..87ecf00ddae2d 100644 --- a/x-pack/test/security_functional/ftr_provider_context.d.ts +++ b/x-pack/test/security_functional/ftr_provider_context.d.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { GenericFtrProviderContext } from '@kbn/test'; +import type { GenericFtrProviderContext } from '@kbn/test'; -import { pageObjects } from '../functional/page_objects'; -import { services } from '../functional/services'; +import type { pageObjects } from '../functional/page_objects'; +import type { services } from '../functional/services'; export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/security_functional/insecure_cluster_warning.config.ts b/x-pack/test/security_functional/insecure_cluster_warning.config.ts index fa89c80653c5d..4a43c9f928f0f 100644 --- a/x-pack/test/security_functional/insecure_cluster_warning.config.ts +++ b/x-pack/test/security_functional/insecure_cluster_warning.config.ts @@ -6,9 +6,11 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; -import { services } from '../functional/services'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { pageObjects } from '../functional/page_objects'; +import { services } from '../functional/services'; // the default export of config files must be a config provider // that returns an object with the projects config values diff --git a/x-pack/test/security_functional/login_selector.config.ts b/x-pack/test/security_functional/login_selector.config.ts index 2a35fc77a0316..d6abf075281f0 100644 --- a/x-pack/test/security_functional/login_selector.config.ts +++ b/x-pack/test/security_functional/login_selector.config.ts @@ -6,9 +6,11 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; -import { services } from '../functional/services'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { pageObjects } from '../functional/page_objects'; +import { services } from '../functional/services'; // the default export of config files must be a config provider // that returns an object with the projects config values diff --git a/x-pack/test/security_functional/oidc.config.ts b/x-pack/test/security_functional/oidc.config.ts index a50975c0ee7bb..4773d2e81497f 100644 --- a/x-pack/test/security_functional/oidc.config.ts +++ b/x-pack/test/security_functional/oidc.config.ts @@ -6,9 +6,11 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; -import { services } from '../functional/services'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { pageObjects } from '../functional/page_objects'; +import { services } from '../functional/services'; // the default export of config files must be a config provider // that returns an object with the projects config values diff --git a/x-pack/test/security_functional/oidc.http2.config.ts b/x-pack/test/security_functional/oidc.http2.config.ts index 79335988411f2..fbba56727f284 100644 --- a/x-pack/test/security_functional/oidc.http2.config.ts +++ b/x-pack/test/security_functional/oidc.http2.config.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { FtrConfigProviderContext } from '@kbn/test'; import { CA_CERT_PATH } from '@kbn/dev-utils'; +import type { FtrConfigProviderContext } from '@kbn/test'; import { configureHTTP2 } from '@kbn/test-suites-src/common/configure_http2'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { diff --git a/x-pack/test/security_functional/plugins/test_endpoints/public/plugin.tsx b/x-pack/test/security_functional/plugins/test_endpoints/public/plugin.tsx index 716187792615d..a8f0be086fe06 100644 --- a/x-pack/test/security_functional/plugins/test_endpoints/public/plugin.tsx +++ b/x-pack/test/security_functional/plugins/test_endpoints/public/plugin.tsx @@ -5,12 +5,12 @@ * 2.0. */ -import type { CoreSetup, Plugin } from '@kbn/core/public'; -import ReactDOM from 'react-dom'; import React from 'react'; -import { debounce, filter, first } from 'rxjs'; -import { timer } from 'rxjs'; -import { SecurityPluginStart } from '@kbn/security-plugin/public'; +import ReactDOM from 'react-dom'; +import { debounce, filter, first, timer } from 'rxjs'; + +import type { CoreSetup, Plugin } from '@kbn/core/public'; +import type { SecurityPluginStart } from '@kbn/security-plugin/public'; export interface PluginStartDependencies { security: SecurityPluginStart; diff --git a/x-pack/test/security_functional/plugins/test_endpoints/server/index.ts b/x-pack/test/security_functional/plugins/test_endpoints/server/index.ts index 93097b9fa712e..178531cd3e6c2 100644 --- a/x-pack/test/security_functional/plugins/test_endpoints/server/index.ts +++ b/x-pack/test/security_functional/plugins/test_endpoints/server/index.ts @@ -5,12 +5,13 @@ * 2.0. */ -import { PluginInitializer, Plugin, CoreSetup } from '@kbn/core/server'; -import { +import type { CoreSetup, Plugin, PluginInitializer } from '@kbn/core/server'; +import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; +import type { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; -import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; + import { initRoutes } from './init_routes'; export interface PluginSetupDependencies { diff --git a/x-pack/test/security_functional/plugins/test_endpoints/server/init_routes.ts b/x-pack/test/security_functional/plugins/test_endpoints/server/init_routes.ts index 70a077305ba39..23fb20c02e42a 100644 --- a/x-pack/test/security_functional/plugins/test_endpoints/server/init_routes.ts +++ b/x-pack/test/security_functional/plugins/test_endpoints/server/init_routes.ts @@ -5,17 +5,19 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; import { errors } from '@elastic/elasticsearch'; -import { CoreSetup, CoreStart, PluginInitializerContext } from '@kbn/core/server'; + +import { schema } from '@kbn/config-schema'; +import type { CoreSetup, CoreStart, PluginInitializerContext } from '@kbn/core/server'; +import { ROUTE_TAG_AUTH_FLOW } from '@kbn/security-plugin/server'; +import { restApiKeySchema } from '@kbn/security-plugin-types-server'; import type { - TaskManagerStartContract, - ConcreteTaskInstance, BulkUpdateTaskResult, + ConcreteTaskInstance, + TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; -import { restApiKeySchema } from '@kbn/security-plugin-types-server'; -import { ROUTE_TAG_AUTH_FLOW } from '@kbn/security-plugin/server'; -import { PluginStartDependencies } from '.'; + +import type { PluginStartDependencies } from '.'; export const SESSION_INDEX_CLEANUP_TASK_NAME = 'session_cleanup'; diff --git a/x-pack/test/security_functional/saml.config.ts b/x-pack/test/security_functional/saml.config.ts index 52ac62336c681..91afd72c97747 100644 --- a/x-pack/test/security_functional/saml.config.ts +++ b/x-pack/test/security_functional/saml.config.ts @@ -6,9 +6,11 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; -import { services } from '../functional/services'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { pageObjects } from '../functional/page_objects'; +import { services } from '../functional/services'; // the default export of config files must be a config provider // that returns an object with the projects config values diff --git a/x-pack/test/security_functional/saml.http2.config.ts b/x-pack/test/security_functional/saml.http2.config.ts index dc9bac7cbdeb9..34131c82e0090 100644 --- a/x-pack/test/security_functional/saml.http2.config.ts +++ b/x-pack/test/security_functional/saml.http2.config.ts @@ -12,8 +12,9 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; + import { CA_CERT_PATH } from '@kbn/dev-utils'; +import type { FtrConfigProviderContext } from '@kbn/test'; import { configureHTTP2 } from '@kbn/test-suites-src/common/configure_http2'; // the default export of config files must be a config provider diff --git a/x-pack/test/security_functional/tests/expired_session/basic_functionality.ts b/x-pack/test/security_functional/tests/expired_session/basic_functionality.ts index bdefbe94c9b22..7997ad5507280 100644 --- a/x-pack/test/security_functional/tests/expired_session/basic_functionality.ts +++ b/x-pack/test/security_functional/tests/expired_session/basic_functionality.ts @@ -5,11 +5,13 @@ * 2.0. */ -import expect from '@kbn/expect'; -import { SESSION_ERROR_REASON_HEADER } from '@kbn/security-plugin/common/constants'; import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { parse } from 'url'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import expect from '@kbn/expect'; +import { SESSION_ERROR_REASON_HEADER } from '@kbn/security-plugin/common/constants'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); diff --git a/x-pack/test/security_functional/tests/expired_session/index.ts b/x-pack/test/security_functional/tests/expired_session/index.ts index d2d741b2b8393..15f6c6ad508e6 100644 --- a/x-pack/test/security_functional/tests/expired_session/index.ts +++ b/x-pack/test/security_functional/tests/expired_session/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security app - expired session', function () { diff --git a/x-pack/test/security_functional/tests/insecure_cluster_warning/index.ts b/x-pack/test/security_functional/tests/insecure_cluster_warning/index.ts index 5878deb83c6a4..e737cf91f1c20 100644 --- a/x-pack/test/security_functional/tests/insecure_cluster_warning/index.ts +++ b/x-pack/test/security_functional/tests/insecure_cluster_warning/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security app - insecure cluster warning', function () { diff --git a/x-pack/test/security_functional/tests/insecure_cluster_warning/insecure_cluster_warning.ts b/x-pack/test/security_functional/tests/insecure_cluster_warning/insecure_cluster_warning.ts index 32640b653a491..9306b290a0ec1 100644 --- a/x-pack/test/security_functional/tests/insecure_cluster_warning/insecure_cluster_warning.ts +++ b/x-pack/test/security_functional/tests/insecure_cluster_warning/insecure_cluster_warning.ts @@ -6,7 +6,8 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common']); diff --git a/x-pack/test/security_functional/tests/login_selector/auth_provider_hint.ts b/x-pack/test/security_functional/tests/login_selector/auth_provider_hint.ts index d99b17d69f461..bcb41c609e512 100644 --- a/x-pack/test/security_functional/tests/login_selector/auth_provider_hint.ts +++ b/x-pack/test/security_functional/tests/login_selector/auth_provider_hint.ts @@ -5,9 +5,11 @@ * 2.0. */ -import expect from '@kbn/expect'; import { parse } from 'url'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import expect from '@kbn/expect'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); diff --git a/x-pack/test/security_functional/tests/login_selector/basic_functionality.ts b/x-pack/test/security_functional/tests/login_selector/basic_functionality.ts index fc808b4c33cbd..49bc6038d88c8 100644 --- a/x-pack/test/security_functional/tests/login_selector/basic_functionality.ts +++ b/x-pack/test/security_functional/tests/login_selector/basic_functionality.ts @@ -5,9 +5,11 @@ * 2.0. */ -import expect from '@kbn/expect'; import { parse } from 'url'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import expect from '@kbn/expect'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); diff --git a/x-pack/test/security_functional/tests/login_selector/index.ts b/x-pack/test/security_functional/tests/login_selector/index.ts index ae81b307987cc..584d5410feb2d 100644 --- a/x-pack/test/security_functional/tests/login_selector/index.ts +++ b/x-pack/test/security_functional/tests/login_selector/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security app - login selector', function () { diff --git a/x-pack/test/security_functional/tests/login_selector/reset_session_page.ts b/x-pack/test/security_functional/tests/login_selector/reset_session_page.ts index 4d7eb666a4bc3..0dd935f86be82 100644 --- a/x-pack/test/security_functional/tests/login_selector/reset_session_page.ts +++ b/x-pack/test/security_functional/tests/login_selector/reset_session_page.ts @@ -6,7 +6,8 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects, updateBaselines }: FtrProviderContext) { const screenshots = getService('screenshots'); diff --git a/x-pack/test/security_functional/tests/oidc/index.ts b/x-pack/test/security_functional/tests/oidc/index.ts index 37490a0193089..2a49047d81a85 100644 --- a/x-pack/test/security_functional/tests/oidc/index.ts +++ b/x-pack/test/security_functional/tests/oidc/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security app - OIDC interactions', function () { diff --git a/x-pack/test/security_functional/tests/oidc/url_capture.ts b/x-pack/test/security_functional/tests/oidc/url_capture.ts index 6553ef193fc3b..624e373c5eeca 100644 --- a/x-pack/test/security_functional/tests/oidc/url_capture.ts +++ b/x-pack/test/security_functional/tests/oidc/url_capture.ts @@ -5,9 +5,11 @@ * 2.0. */ -import expect from '@kbn/expect'; import { parse } from 'url'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import expect from '@kbn/expect'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const find = getService('find'); diff --git a/x-pack/test/security_functional/tests/saml/index.ts b/x-pack/test/security_functional/tests/saml/index.ts index ebf97ebf8edfb..1cf0c4cedc7bb 100644 --- a/x-pack/test/security_functional/tests/saml/index.ts +++ b/x-pack/test/security_functional/tests/saml/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security app - SAML interactions', function () { diff --git a/x-pack/test/security_functional/tests/saml/url_capture.ts b/x-pack/test/security_functional/tests/saml/url_capture.ts index 0193d3d870701..72bb342716cd0 100644 --- a/x-pack/test/security_functional/tests/saml/url_capture.ts +++ b/x-pack/test/security_functional/tests/saml/url_capture.ts @@ -5,9 +5,11 @@ * 2.0. */ -import expect from '@kbn/expect'; import { parse } from 'url'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import expect from '@kbn/expect'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const find = getService('find'); diff --git a/x-pack/test/security_functional/tests/user_profiles/client_side_apis.ts b/x-pack/test/security_functional/tests/user_profiles/client_side_apis.ts index 0e1fb7879d81a..c6675eb526632 100644 --- a/x-pack/test/security_functional/tests/user_profiles/client_side_apis.ts +++ b/x-pack/test/security_functional/tests/user_profiles/client_side_apis.ts @@ -5,10 +5,12 @@ * 2.0. */ -import expect from '@kbn/expect'; import { parse as parseCookie } from 'tough-cookie'; + +import expect from '@kbn/expect'; import { adminTestUser } from '@kbn/test'; -import { FtrProviderContext } from '../../ftr_provider_context'; + +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['security', 'common']); diff --git a/x-pack/test/security_functional/tests/user_profiles/index.ts b/x-pack/test/security_functional/tests/user_profiles/index.ts index 85c74150dd3bd..4bb317365845f 100644 --- a/x-pack/test/security_functional/tests/user_profiles/index.ts +++ b/x-pack/test/security_functional/tests/user_profiles/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('security app - user profiles', function () { diff --git a/x-pack/test/security_functional/user_profiles.config.ts b/x-pack/test/security_functional/user_profiles.config.ts index e5b65db64c383..c52d29cd392d6 100644 --- a/x-pack/test/security_functional/user_profiles.config.ts +++ b/x-pack/test/security_functional/user_profiles.config.ts @@ -6,9 +6,11 @@ */ import { resolve } from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; -import { services } from '../functional/services'; + +import type { FtrConfigProviderContext } from '@kbn/test'; + import { pageObjects } from '../functional/page_objects'; +import { services } from '../functional/services'; // the default export of config files must be a config provider // that returns an object with the projects config values diff --git a/x-pack/test/security_solution_api_integration/config/ess/config.base.ts b/x-pack/test/security_solution_api_integration/config/ess/config.base.ts index 3ab6d5059fd07..a0d2ee79a7b46 100644 --- a/x-pack/test/security_solution_api_integration/config/ess/config.base.ts +++ b/x-pack/test/security_solution_api_integration/config/ess/config.base.ts @@ -82,7 +82,6 @@ export function createTestConfig(options: CreateTestConfigOptions, testFiles?: s '--xpack.ruleRegistry.unsafe.legacyMultiTenancy.enabled=true', `--xpack.securitySolution.enableExperimental=${JSON.stringify([ 'previewTelemetryUrlEnabled', - 'loggingRequestsEnabled', 'riskScoringPersistence', 'riskScoringRoutesEnabled', ])}`, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts index ce949d5cc23fc..137ee1f67b9b3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts @@ -17,6 +17,5 @@ export default createTestConfig({ 'testing_ignored.constant', '/testing_regex*/', ])}`, // See tests within the file "ignore_fields.ts" which use these values in "alertIgnoreFields" - `--xpack.securitySolution.enableExperimental=${JSON.stringify(['loggingRequestsEnabled'])}`, ], }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts index aff2ccc6bccb3..9077873274fa5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts @@ -1190,8 +1190,7 @@ export default ({ getService }: FtrProviderContext) => { }); }); - // skipped on MKI since feature flags are not supported there - describe('@skipInServerlessMKI preview logged requests', () => { + describe('preview logged requests', () => { it('should not return requests property when not enabled', async () => { const { logs } = await previewRule({ supertest, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts index 166a62b9b08ad..ee976de14186d 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts @@ -1409,8 +1409,7 @@ export default ({ getService }: FtrProviderContext) => { }); }); - // skipped on MKI since feature flags are not supported there - describe('@skipInServerlessMKI preview logged requests', () => { + describe('preview logged requests', () => { let rule: EsqlRuleCreateProps; let id: string; beforeEach(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts index a567eb78a776d..41f207c90f319 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts @@ -16,6 +16,9 @@ import { removeServerGeneratedPropertiesIncludingRuleId, getSimpleRuleOutputWithoutRuleId, updateUsername, + createHistoricalPrebuiltRuleAssetSavedObjects, + installPrebuiltRules, + createRuleAssetSavedObject, } from '../../../utils'; import { createAlertsIndex, @@ -238,6 +241,25 @@ export default ({ getService }: FtrProviderContext) => { }); }); + it('throws an error if rule has external rule source and non-customizable fields are changed', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ rule_id: 'rule-1', author: ['elastic'] }), + ]); + await installPrebuiltRules(es, supertest); + + const { body } = await securitySolutionApi + .patchRule({ + body: { + rule_id: 'rule-1', + author: ['new user'], + }, + }) + .expect(400); + + expect(body.message).toEqual('Cannot update "author" field for prebuilt rules'); + }); + describe('max signals', () => { afterEach(async () => { await deleteAllRules(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts index 086909fc4945b..7929b912768ff 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts @@ -16,6 +16,9 @@ import { getSimpleRuleOutputWithoutRuleId, removeServerGeneratedPropertiesIncludingRuleId, updateUsername, + createHistoricalPrebuiltRuleAssetSavedObjects, + installPrebuiltRules, + createRuleAssetSavedObject, } from '../../../utils'; import { createAlertsIndex, @@ -347,6 +350,41 @@ export default ({ getService }: FtrProviderContext) => { }, ]); }); + + it('throws an error if rule has external rule source and non-customizable fields are changed', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ rule_id: 'rule-1', author: ['elastic'] }), + createRuleAssetSavedObject({ rule_id: 'rule-2', license: 'basic' }), + ]); + await installPrebuiltRules(es, supertest); + + const { body } = await securitySolutionApi + .bulkPatchRules({ + body: [ + { rule_id: 'rule-1', author: ['new user'] }, + { rule_id: 'rule-2', license: 'new license' }, + ], + }) + .expect(200); + + expect([body[0], body[1]]).toEqual([ + { + error: { + message: 'Cannot update "author" field for prebuilt rules', + status_code: 400, + }, + rule_id: 'rule-1', + }, + { + error: { + message: 'Cannot update "license" field for prebuilt rules', + status_code: 400, + }, + rule_id: 'rule-2', + }, + ]); + }); }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts index 60e7bfe3ff88f..c84236a14eb37 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts @@ -18,6 +18,9 @@ import { getSimpleMlRuleUpdate, getSimpleRule, updateUsername, + createHistoricalPrebuiltRuleAssetSavedObjects, + installPrebuiltRules, + createRuleAssetSavedObject, } from '../../../utils'; import { createAlertsIndex, @@ -309,6 +312,33 @@ export default ({ getService }: FtrProviderContext) => { expect(updatedRuleResponse).toMatchObject(expectedRule); }); }); + + it('throws an error if rule has external rule source and non-customizable fields are changed', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ rule_id: 'rule-1', license: 'elastic' }), + ]); + await installPrebuiltRules(es, supertest); + + const { body: existingRule } = await securitySolutionApi + .readRule({ + query: { rule_id: 'rule-1' }, + }) + .expect(200); + + const { body } = await securitySolutionApi + .updateRule({ + body: getCustomQueryRuleParams({ + ...existingRule, + rule_id: 'rule-1', + id: undefined, + license: 'new license', + }), + }) + .expect(400); + + expect(body.message).toEqual('Cannot update "license" field for prebuilt rules'); + }); }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts index f9faee0481bf6..cdca9e3ca6e1a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts @@ -17,6 +17,9 @@ import { getSimpleRuleUpdate, getSimpleRule, updateUsername, + createHistoricalPrebuiltRuleAssetSavedObjects, + installPrebuiltRules, + createRuleAssetSavedObject, } from '../../../utils'; import { createAlertsIndex, @@ -370,6 +373,30 @@ export default ({ getService }: FtrProviderContext) => { }, ]); }); + + it('throws an error if rule has external rule source and non-customizable fields are changed', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObject({ rule_id: 'rule-1', author: ['elastic'] }), + ]); + await installPrebuiltRules(es, supertest); + + const { body } = await securitySolutionApi + .bulkUpdateRules({ + body: [getCustomQueryRuleParams({ rule_id: 'rule-1', author: ['new user'] })], + }) + .expect(200); + + expect([body[0]]).toEqual([ + { + error: { + message: 'Cannot update "author" field for prebuilt rules', + status_code: 400, + }, + rule_id: 'rule-1', + }, + ]); + }); }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules.ts index b3b58ac7880f8..c43d08a805ca8 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules.ts @@ -31,6 +31,7 @@ import { getRuleSavedObjectWithLegacyInvestigationFields, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, createRuleThroughAlertingEndpoint, + getCustomQueryRuleParams, } from '../../../utils'; import { createRule, @@ -1140,7 +1141,7 @@ export default ({ getService }: FtrProviderContext) => { await installMockPrebuiltRules(supertest, es); const immutableRule = await fetchRule(supertest, { ruleId: ELASTIC_SECURITY_RULE_ID }); const hookAction = await createWebHookRuleAction(supertest); - const newRuleToUpdate = getSimpleRule(immutableRule.rule_id); + const newRuleToUpdate = getCustomQueryRuleParams({ rule_id: immutableRule.rule_id }); const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, false, newRuleToUpdate); await updateRule(supertest, ruleToUpdate); @@ -1161,7 +1162,7 @@ export default ({ getService }: FtrProviderContext) => { ...omittedFields } = foundRule; expect(omittedFields).to.eql({ - rule_name: 'Simple Rule Query', + rule_name: 'Custom query rule', rule_type: 'query', enabled: false, elastic_rule: true, @@ -1197,7 +1198,7 @@ export default ({ getService }: FtrProviderContext) => { await installMockPrebuiltRules(supertest, es); const immutableRule = await fetchRule(supertest, { ruleId: ELASTIC_SECURITY_RULE_ID }); const hookAction = await createWebHookRuleAction(supertest); - const newRuleToUpdate = getSimpleRule(immutableRule.rule_id); + const newRuleToUpdate = getCustomQueryRuleParams({ rule_id: immutableRule.rule_id }); const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, true, newRuleToUpdate); await updateRule(supertest, ruleToUpdate); @@ -1218,7 +1219,7 @@ export default ({ getService }: FtrProviderContext) => { ...omittedFields } = foundRule; expect(omittedFields).to.eql({ - rule_name: 'Simple Rule Query', + rule_name: 'Custom query rule', rule_type: 'query', enabled: true, elastic_rule: true, @@ -1254,7 +1255,7 @@ export default ({ getService }: FtrProviderContext) => { await installMockPrebuiltRules(supertest, es); const immutableRule = await fetchRule(supertest, { ruleId: ELASTIC_SECURITY_RULE_ID }); const hookAction = await createWebHookRuleAction(supertest); - const newRuleToUpdate = getSimpleRule(immutableRule.rule_id, false); + const newRuleToUpdate = getCustomQueryRuleParams({ rule_id: immutableRule.rule_id }); await updateRule(supertest, newRuleToUpdate); await createLegacyRuleAction(supertest, immutableRule.id, hookAction.id); @@ -1275,7 +1276,7 @@ export default ({ getService }: FtrProviderContext) => { ...omittedFields } = foundRule; expect(omittedFields).to.eql({ - rule_name: 'Simple Rule Query', + rule_name: 'Custom query rule', rule_type: 'query', enabled: false, elastic_rule: true, @@ -1311,7 +1312,10 @@ export default ({ getService }: FtrProviderContext) => { await installMockPrebuiltRules(supertest, es); const immutableRule = await fetchRule(supertest, { ruleId: ELASTIC_SECURITY_RULE_ID }); const hookAction = await createWebHookRuleAction(supertest); - const newRuleToUpdate = getSimpleRule(immutableRule.rule_id, true); + const newRuleToUpdate = getCustomQueryRuleParams({ + rule_id: immutableRule.rule_id, + enabled: true, + }); await updateRule(supertest, newRuleToUpdate); await createLegacyRuleAction(supertest, immutableRule.id, hookAction.id); @@ -1332,7 +1336,7 @@ export default ({ getService }: FtrProviderContext) => { ...omittedFields } = foundRule; expect(omittedFields).to.eql({ - rule_name: 'Simple Rule Query', + rule_name: 'Custom query rule', rule_type: 'query', enabled: true, elastic_rule: true, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules_legacy_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules_legacy_action.ts index e3754d9a09b60..f85f317e2da07 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules_legacy_action.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules_legacy_action.ts @@ -21,13 +21,13 @@ import { fetchRule, getRuleWithWebHookAction, getSimpleMlRule, - getSimpleRule, getSimpleThreatMatch, getStats, getThresholdRuleForAlertTesting, installMockPrebuiltRules, updateRule, deleteAllEventLogExecutionEvents, + getCustomQueryRuleParams, } from '../../../utils'; import { createRule, @@ -408,7 +408,7 @@ export default ({ getService }: FtrProviderContext) => { await installMockPrebuiltRules(supertest, es); const immutableRule = await fetchRule(supertest, { ruleId: ELASTIC_SECURITY_RULE_ID }); const hookAction = await createWebHookRuleAction(supertest); - const newRuleToUpdate = getSimpleRule(immutableRule.rule_id, false); + const newRuleToUpdate = getCustomQueryRuleParams({ rule_id: immutableRule.rule_id }); await updateRule(supertest, newRuleToUpdate); await createLegacyRuleAction(supertest, immutableRule.id, hookAction.id); @@ -429,7 +429,7 @@ export default ({ getService }: FtrProviderContext) => { ...omittedFields } = foundRule; expect(omittedFields).to.eql({ - rule_name: 'Simple Rule Query', + rule_name: 'Custom query rule', rule_type: 'query', enabled: false, elastic_rule: true, @@ -465,7 +465,10 @@ export default ({ getService }: FtrProviderContext) => { await installMockPrebuiltRules(supertest, es); const immutableRule = await fetchRule(supertest, { ruleId: ELASTIC_SECURITY_RULE_ID }); const hookAction = await createWebHookRuleAction(supertest); - const newRuleToUpdate = getSimpleRule(immutableRule.rule_id, true); + const newRuleToUpdate = getCustomQueryRuleParams({ + rule_id: immutableRule.rule_id, + enabled: true, + }); await updateRule(supertest, newRuleToUpdate); await createLegacyRuleAction(supertest, immutableRule.id, hookAction.id); @@ -486,7 +489,7 @@ export default ({ getService }: FtrProviderContext) => { ...omittedFields } = foundRule; expect(omittedFields).to.eql({ - rule_name: 'Simple Rule Query', + rule_name: 'Custom query rule', rule_type: 'query', enabled: true, elastic_rule: true, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_params/get_custom_query_rule_params.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_params/get_custom_query_rule_params.ts index b561d3e8dc023..a5c5fe00ed700 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_params/get_custom_query_rule_params.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_params/get_custom_query_rule_params.ts @@ -29,6 +29,7 @@ export function getCustomQueryRuleParams( index: ['logs-*'], interval: '100m', from: 'now-6m', + author: [], enabled: false, ...rewrites, }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/init_and_status_apis.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/init_and_status_apis.ts index bd3493b82d348..19a9bb85326fa 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/init_and_status_apis.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/init_and_status_apis.ts @@ -26,8 +26,7 @@ export default ({ getService }: FtrProviderContext) => { const riskEngineRoutes = riskEngineRouteHelpersFactory(supertest); const log = getService('log'); - // Failing: See https://github.com/elastic/kibana/issues/191637 - describe.skip('@ess @serverless @serverlessQA init_and_status_apis', () => { + describe('@ess @serverless @serverlessQA init_and_status_apis', () => { before(async () => { await riskEngineRoutes.cleanUp(); }); @@ -298,8 +297,8 @@ export default ({ getService }: FtrProviderContext) => { firstResponse?.saved_objects?.[0]?.id ); }); - - describe('remove legacy risk score transform', function () { + // Failing: See https://github.com/elastic/kibana/issues/191637 + describe.skip('remove legacy risk score transform', function () { this.tags('skipFIPS'); it('should remove legacy risk score transform if it exists', async () => { await installLegacyRiskScore({ supertest }); diff --git a/x-pack/test/security_solution_cypress/config.ts b/x-pack/test/security_solution_cypress/config.ts index 05bc2e381527a..f02968945087d 100644 --- a/x-pack/test/security_solution_cypress/config.ts +++ b/x-pack/test/security_solution_cypress/config.ts @@ -44,7 +44,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { // See https://github.com/elastic/kibana/pull/125396 for details '--xpack.alerting.rules.minimumScheduleInterval.value=1s', '--xpack.ruleRegistry.unsafe.legacyMultiTenancy.enabled=true', - `--xpack.securitySolution.enableExperimental=${JSON.stringify(['loggingRequestsEnabled'])}`, // mock cloud to enable the guided onboarding tour in e2e tests '--xpack.cloud.id=test', `--home.disableWelcomeScreen=true`, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts index 0045a79ff4394..64423a921e595 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts @@ -67,7 +67,8 @@ const workaroundForResizeObserver = () => } }); -describe( +// Failing: See https://github.com/elastic/kibana/issues/184558 +describe.skip( 'Detection ES|QL rules, creation', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'], diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/esql_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/esql_rule.cy.ts index 9fa45987407f0..34f301602b692 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/esql_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/esql_rule.cy.ts @@ -55,7 +55,9 @@ const expectedValidEsqlQuery = 'from auditbeat* | stats _count=count(event.category) by event.category'; // Skipping in MKI due to flake -describe( +// Failing: See https://github.com/elastic/kibana/issues/184557 +// Failing: See https://github.com/elastic/kibana/issues/184556 +describe.skip( 'Detection ES|QL rules, edit', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'], diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/preview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/preview.cy.ts index c2e41c9d4680c..268968c76ecc0 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/preview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/preview.cy.ts @@ -33,11 +33,6 @@ describe( 'Detection rules, preview', { tags: ['@ess', '@serverless'], - env: { - kbnServerArgs: [ - `--xpack.securitySolution.enableExperimental=${JSON.stringify(['loggingRequestsEnabled'])}`, - ], - }, }, () => { beforeEach(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/backfill_group.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/backfill_group.cy.ts index f747e6be43e5a..6466c20dfde21 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/backfill_group.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/backfill_group.cy.ts @@ -34,7 +34,7 @@ import { describe( 'Backfill groups', { - tags: ['@ess', '@serverless'], + tags: ['@ess', '@serverless', '@skipInServerlessMKI'], }, function () { before(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/vulnerabilities_contextual_flyout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/vulnerabilities_contextual_flyout.cy.ts index 591d458af56c1..fb83df1c79141 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/vulnerabilities_contextual_flyout.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/vulnerabilities_contextual_flyout.cy.ts @@ -138,7 +138,8 @@ const deleteDataStream = () => { }); }; -describe('Alert Host details expandable flyout', { tags: ['@ess', '@serverless'] }, () => { +// skipping because failure on MKI environment (https://buildkite.com/elastic/kibana-serverless-security-solution-quality-gate-investigations/builds/1390#01927579-caed-41bc-9440-3cf29629a263) +describe.skip('Alert Host details expandable flyout', { tags: ['@ess', '@serverless'] }, () => { beforeEach(() => { deleteAlertsAndRules(); login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/threat_intelligence/indicators.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/threat_intelligence/indicators.cy.ts index ebfc5d4e9a0cb..b0e5764469459 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/threat_intelligence/indicators.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/threat_intelligence/indicators.cy.ts @@ -298,7 +298,7 @@ describe('Multiple indicators', { tags: ['@ess'] }, () => { cy.log('should reload the data when refresh button is pressed'); - cy.intercept(/bsearch/).as('search'); + cy.intercept('POST', '/internal/search/threatIntelligenceSearchStrategy').as('search'); cy.get(REFRESH_BUTTON).should('exist').click(); cy.wait('@search'); }); diff --git a/x-pack/test/security_solution_cypress/serverless_config.ts b/x-pack/test/security_solution_cypress/serverless_config.ts index 71a63b697187f..f3f04dda79dbb 100644 --- a/x-pack/test/security_solution_cypress/serverless_config.ts +++ b/x-pack/test/security_solution_cypress/serverless_config.ts @@ -34,7 +34,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { { product_line: 'endpoint', product_tier: 'complete' }, { product_line: 'cloud', product_tier: 'complete' }, ])}`, - `--xpack.securitySolution.enableExperimental=${JSON.stringify(['loggingRequestsEnabled'])}`, '--csp.strict=false', '--csp.warnLegacyBrowsers=false', ], diff --git a/x-pack/test/spaces_api_integration/common/config.ts b/x-pack/test/spaces_api_integration/common/config.ts index f2074fac8bea1..466b34b65ce84 100644 --- a/x-pack/test/spaces_api_integration/common/config.ts +++ b/x-pack/test/spaces_api_integration/common/config.ts @@ -6,8 +6,9 @@ */ import path from 'path'; + import { REPO_ROOT } from '@kbn/repo-info'; -import { FtrConfigProviderContext } from '@kbn/test'; +import type { FtrConfigProviderContext } from '@kbn/test'; interface CreateTestConfigOptions { license: string; diff --git a/x-pack/test/spaces_api_integration/common/ftr_provider_context.d.ts b/x-pack/test/spaces_api_integration/common/ftr_provider_context.d.ts index aa56557c09df8..b05bbc8f6318d 100644 --- a/x-pack/test/spaces_api_integration/common/ftr_provider_context.d.ts +++ b/x-pack/test/spaces_api_integration/common/ftr_provider_context.d.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { GenericFtrProviderContext } from '@kbn/test'; +import type { GenericFtrProviderContext } from '@kbn/test'; -import { services } from './services'; +import type { services } from './services'; export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts b/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts index b66c4a02a5bd6..2f93cc09fd032 100644 --- a/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts +++ b/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts @@ -5,8 +5,9 @@ * 2.0. */ -import { Agent as SuperTestAgent } from 'supertest'; import type { Client } from '@elastic/elasticsearch'; +import type { Agent as SuperTestAgent } from 'supertest'; + import { AUTHENTICATION } from './authentication'; export const createUsersAndRoles = async (es: Client, supertest: SuperTestAgent) => { diff --git a/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts b/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts index 8de4482474d8e..3c30208933550 100644 --- a/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts +++ b/x-pack/test/spaces_api_integration/common/lib/space_test_utils.ts @@ -6,6 +6,7 @@ */ import type { Client } from '@elastic/elasticsearch'; + import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; diff --git a/x-pack/test/spaces_api_integration/common/plugins/spaces_test_plugin/server/plugin.ts b/x-pack/test/spaces_api_integration/common/plugins/spaces_test_plugin/server/plugin.ts index 07cc3bcd28c1b..56a226def6ba4 100644 --- a/x-pack/test/spaces_api_integration/common/plugins/spaces_test_plugin/server/plugin.ts +++ b/x-pack/test/spaces_api_integration/common/plugins/spaces_test_plugin/server/plugin.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CoreSetup } from '@kbn/core/server'; +import type { CoreSetup } from '@kbn/core/server'; export class Plugin { constructor() {} diff --git a/x-pack/test/spaces_api_integration/common/services.ts b/x-pack/test/spaces_api_integration/common/services.ts index 8b24d9b23e675..44ca7ec39c582 100644 --- a/x-pack/test/spaces_api_integration/common/services.ts +++ b/x-pack/test/spaces_api_integration/common/services.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { services as commonServices } from '../../common/services'; import { services as apiIntegrationServices } from '../../api_integration/services'; +import { services as commonServices } from '../../common/services'; export const services = { ...commonServices, diff --git a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts index 8de127aeaefe0..4cb2506977123 100644 --- a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts +++ b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts @@ -5,18 +5,20 @@ * 2.0. */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import expect from '@kbn/expect'; -import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; -import { CopyResponse } from '@kbn/spaces-plugin/server/lib/copy_to_spaces'; -import { - SavedObjectsImportFailure, +import type { SuperTest } from 'supertest'; + +import type { SavedObjectsImportAmbiguousConflictError, + SavedObjectsImportFailure, } from '@kbn/core/server'; -import { SuperTest } from 'supertest'; -import { getAggregatedSpaceData, getUrlPrefix } from '../lib/space_test_utils'; -import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; +import expect from '@kbn/expect'; +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; +import type { CopyResponse } from '@kbn/spaces-plugin/server/lib/copy_to_spaces'; + import { getTestDataLoader, SPACE_1, SPACE_2 } from '../../../common/lib/test_data_loader'; import type { FtrProviderContext } from '../ftr_provider_context'; +import { getAggregatedSpaceData, getUrlPrefix } from '../lib/space_test_utils'; +import type { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; type TestResponse = Record; diff --git a/x-pack/test/spaces_api_integration/common/suites/create.ts b/x-pack/test/spaces_api_integration/common/suites/create.ts index ce3113ec9639c..3c65ba8aba156 100644 --- a/x-pack/test/spaces_api_integration/common/suites/create.ts +++ b/x-pack/test/spaces_api_integration/common/suites/create.ts @@ -5,10 +5,12 @@ * 2.0. */ +import type { SuperTest } from 'supertest'; + import expect from '@kbn/expect'; -import { SuperTest } from 'supertest'; + import { getTestScenariosForSpace } from '../lib/space_test_utils'; -import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; +import type { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; interface CreateTest { statusCode: number; diff --git a/x-pack/test/spaces_api_integration/common/suites/delete.ts b/x-pack/test/spaces_api_integration/common/suites/delete.ts index fd04b79fd1ef5..1a75bc219ae1f 100644 --- a/x-pack/test/spaces_api_integration/common/suites/delete.ts +++ b/x-pack/test/spaces_api_integration/common/suites/delete.ts @@ -5,13 +5,15 @@ * 2.0. */ -import expect from '@kbn/expect'; -import { SuperTest } from 'supertest'; import type { Client } from '@elastic/elasticsearch'; +import type { SuperTest } from 'supertest'; + import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; -import { getAggregatedSpaceData, getTestScenariosForSpace } from '../lib/space_test_utils'; +import expect from '@kbn/expect'; + import { MULTI_NAMESPACE_SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases'; -import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; +import { getAggregatedSpaceData, getTestScenariosForSpace } from '../lib/space_test_utils'; +import type { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; interface DeleteTest { statusCode: number; diff --git a/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts b/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts index 5889a10479f31..a00729e7eca8c 100644 --- a/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts +++ b/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts @@ -5,18 +5,20 @@ * 2.0. */ -import expect from '@kbn/expect'; -import type { Agent as SuperTestAgent } from 'supertest'; import type { Client } from '@elastic/elasticsearch'; +import type { Agent as SuperTestAgent } from 'supertest'; + import type { LegacyUrlAlias } from '@kbn/core-saved-objects-base-server-internal'; import { MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; -import { SPACES } from '../lib/spaces'; +import expect from '@kbn/expect'; + import { getUrlPrefix } from '../../../saved_object_api_integration/common/lib/saved_object_test_utils'; import type { ExpectResponseBody, TestDefinition, TestSuite, } from '../../../saved_object_api_integration/common/lib/types'; +import { SPACES } from '../lib/spaces'; export interface DisableLegacyUrlAliasesTestDefinition extends TestDefinition { request: { diff --git a/x-pack/test/spaces_api_integration/common/suites/get.ts b/x-pack/test/spaces_api_integration/common/suites/get.ts index a733bd21c5fc2..cb3879591ed25 100644 --- a/x-pack/test/spaces_api_integration/common/suites/get.ts +++ b/x-pack/test/spaces_api_integration/common/suites/get.ts @@ -5,10 +5,12 @@ * 2.0. */ +import type { SuperAgent } from 'superagent'; + import expect from '@kbn/expect'; -import { SuperAgent } from 'superagent'; + import { getTestScenariosForSpace } from '../lib/space_test_utils'; -import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; +import type { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; interface GetTest { statusCode: number; diff --git a/x-pack/test/spaces_api_integration/common/suites/get_all.ts b/x-pack/test/spaces_api_integration/common/suites/get_all.ts index 88625c3d9b51e..236c98d9364b9 100644 --- a/x-pack/test/spaces_api_integration/common/suites/get_all.ts +++ b/x-pack/test/spaces_api_integration/common/suites/get_all.ts @@ -5,10 +5,12 @@ * 2.0. */ +import type { SuperTest } from 'supertest'; + import expect from '@kbn/expect'; -import { SuperTest } from 'supertest'; + import { getTestScenariosForSpace } from '../lib/space_test_utils'; -import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; +import type { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; interface GetAllTest { statusCode: number; diff --git a/x-pack/test/spaces_api_integration/common/suites/get_shareable_references.ts b/x-pack/test/spaces_api_integration/common/suites/get_shareable_references.ts index c1630bc288169..553aedbab958b 100644 --- a/x-pack/test/spaces_api_integration/common/suites/get_shareable_references.ts +++ b/x-pack/test/spaces_api_integration/common/suites/get_shareable_references.ts @@ -5,24 +5,26 @@ * 2.0. */ -import expect from '@kbn/expect'; -import { deepFreeze } from '@kbn/std'; -import { Agent as SuperTestAgent } from 'supertest'; -import { - SavedObjectsCollectMultiNamespaceReferencesResponse, +import type { Agent as SuperTestAgent } from 'supertest'; + +import type { SavedObjectReferenceWithContext, + SavedObjectsCollectMultiNamespaceReferencesResponse, } from '@kbn/core/server'; -import { MULTI_NAMESPACE_SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases'; -import { SPACES } from '../lib/spaces'; +import expect from '@kbn/expect'; +import { deepFreeze } from '@kbn/std'; + import { expectResponses, getUrlPrefix, } from '../../../saved_object_api_integration/common/lib/saved_object_test_utils'; -import { +import type { ExpectResponseBody, TestDefinition, TestSuite, } from '../../../saved_object_api_integration/common/lib/types'; +import { MULTI_NAMESPACE_SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases'; +import { SPACES } from '../lib/spaces'; export interface GetShareableReferencesTestDefinition extends TestDefinition { request: { diff --git a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts index 4c3e9c834a0f5..e07d56a95ba24 100644 --- a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts +++ b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts @@ -5,15 +5,17 @@ * 2.0. */ +import type { SuperTest } from 'supertest'; + +import type { SavedObject } from '@kbn/core/server'; import expect from '@kbn/expect'; -import { SavedObject } from '@kbn/core/server'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; -import { CopyResponse } from '@kbn/spaces-plugin/server/lib/copy_to_spaces'; -import { SuperTest } from 'supertest'; -import { getUrlPrefix } from '../lib/space_test_utils'; -import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; -import type { FtrProviderContext } from '../ftr_provider_context'; +import type { CopyResponse } from '@kbn/spaces-plugin/server/lib/copy_to_spaces'; + import { getTestDataLoader, SPACE_1, SPACE_2 } from '../../../common/lib/test_data_loader'; +import type { FtrProviderContext } from '../ftr_provider_context'; +import { getUrlPrefix } from '../lib/space_test_utils'; +import type { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; type TestResponse = Record; diff --git a/x-pack/test/spaces_api_integration/common/suites/update.ts b/x-pack/test/spaces_api_integration/common/suites/update.ts index 41e5d97f20514..62226bf4dbb8d 100644 --- a/x-pack/test/spaces_api_integration/common/suites/update.ts +++ b/x-pack/test/spaces_api_integration/common/suites/update.ts @@ -5,10 +5,12 @@ * 2.0. */ +import type { SuperTest } from 'supertest'; + import expect from '@kbn/expect'; -import { SuperTest } from 'supertest'; + import { getUrlPrefix } from '../lib/space_test_utils'; -import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; +import type { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; interface UpdateTest { statusCode: number; diff --git a/x-pack/test/spaces_api_integration/common/suites/update_objects_spaces.ts b/x-pack/test/spaces_api_integration/common/suites/update_objects_spaces.ts index 777581d9aa5a0..05ed64e1e6047 100644 --- a/x-pack/test/spaces_api_integration/common/suites/update_objects_spaces.ts +++ b/x-pack/test/spaces_api_integration/common/suites/update_objects_spaces.ts @@ -5,26 +5,26 @@ * 2.0. */ -import expect from '@kbn/expect'; import type { Client } from '@elastic/elasticsearch'; import type { SearchTotalHits } from '@elastic/elasticsearch/lib/api/types'; -import { without, uniq } from 'lodash'; -import { Agent as SuperTestAgent } from 'supertest'; -import { - SavedObjectsErrorHelpers, - SavedObjectsUpdateObjectsSpacesResponse, -} from '@kbn/core/server'; +import { uniq, without } from 'lodash'; +import type { Agent as SuperTestAgent } from 'supertest'; + +import type { SavedObjectsUpdateObjectsSpacesResponse } from '@kbn/core/server'; +import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; -import { SPACES } from '../lib/spaces'; +import expect from '@kbn/expect'; + import { expectResponses, getUrlPrefix, } from '../../../saved_object_api_integration/common/lib/saved_object_test_utils'; -import { +import type { ExpectResponseBody, TestDefinition, TestSuite, } from '../../../saved_object_api_integration/common/lib/types'; +import { SPACES } from '../lib/spaces'; export interface UpdateObjectsSpacesTestDefinition extends TestDefinition { request: { diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/copy_to_space/copy_to_space.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/copy_to_space/copy_to_space.ts index 90b2cc67439d7..b7e10cb500e80 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/copy_to_space/copy_to_space.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/copy_to_space/copy_to_space.ts @@ -5,10 +5,10 @@ * 2.0. */ +import type { FtrProviderContext } from '../../../common/ftr_provider_context'; import { AUTHENTICATION } from '../../../common/lib/authentication'; import { SPACES } from '../../../common/lib/spaces'; import { copyToSpaceTestSuiteFactory } from '../../../common/suites/copy_to_space'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function copyToSpaceSpacesAndSecuritySuite(context: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/copy_to_space/index.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/copy_to_space/index.ts index c1edb1e5e5ac1..79a6e69cb6f32 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/copy_to_space/index.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/copy_to_space/index.ts @@ -5,8 +5,8 @@ * 2.0. */ +import type { FtrProviderContext } from '../../../common/ftr_provider_context'; import { createUsersAndRoles } from '../../../common/lib/create_users_and_roles'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function ({ loadTestFile, getService }: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/create.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/create.ts index 3195f35c734cb..6e5bc1649cb3f 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/create.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/create.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { SuperTest } from 'supertest'; +import type { SuperTest } from 'supertest'; + +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { AUTHENTICATION } from '../../common/lib/authentication'; import { SPACES } from '../../common/lib/spaces'; import { createTestSuiteFactory } from '../../common/suites/create'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createSpacesOnlySuite({ getService }: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts index 60bcb91125858..01fd306a7f558 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { SuperTest } from 'supertest'; +import type { SuperTest } from 'supertest'; + +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { AUTHENTICATION } from '../../common/lib/authentication'; import { SPACES } from '../../common/lib/spaces'; import { deleteTestSuiteFactory } from '../../common/suites/delete'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function deleteSpaceTestSuite({ getService }: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/disable_legacy_url_aliases.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/disable_legacy_url_aliases.ts index cd00a3d8b7ee6..2498ce166bcce 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/disable_legacy_url_aliases.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/disable_legacy_url_aliases.ts @@ -5,16 +5,16 @@ * 2.0. */ -import { SPACES } from '../../common/lib/spaces'; import { getTestScenarios } from '../../../saved_object_api_integration/common/lib/saved_object_test_utils'; -import { TestUser } from '../../../saved_object_api_integration/common/lib/types'; +import type { TestUser } from '../../../saved_object_api_integration/common/lib/types'; +import type { FtrProviderContext } from '../../common/ftr_provider_context'; +import { SPACES } from '../../common/lib/spaces'; +import type { DisableLegacyUrlAliasesTestDefinition } from '../../common/suites/disable_legacy_url_aliases'; import { disableLegacyUrlAliasesTestSuiteFactory, - TEST_CASE_TARGET_TYPE, TEST_CASE_SOURCE_ID, - DisableLegacyUrlAliasesTestDefinition, + TEST_CASE_TARGET_TYPE, } from '../../common/suites/disable_legacy_url_aliases'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; const { DEFAULT: { spaceId: DEFAULT_SPACE_ID }, diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts index 7354138b7987e..cc24ffa0d891a 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { SuperTest } from 'supertest'; +import type { SuperTest } from 'supertest'; + +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { AUTHENTICATION } from '../../common/lib/authentication'; import { SPACES } from '../../common/lib/spaces'; import { getTestSuiteFactory } from '../../common/suites/get'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function getSpaceTestSuite({ getService }: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts index d2c3b8be03be2..1c2db5f6bcd7c 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { SuperTest } from 'supertest'; +import type { SuperTest } from 'supertest'; + +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { AUTHENTICATION } from '../../common/lib/authentication'; import { SPACES } from '../../common/lib/spaces'; import { getAllTestSuiteFactory } from '../../common/suites/get_all'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function getAllSpacesTestSuite({ getService }: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_shareable_references.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_shareable_references.ts index d3466dd511e82..093e01dd8fbbf 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_shareable_references.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_shareable_references.ts @@ -5,17 +5,19 @@ * 2.0. */ -import { SPACES } from '../../common/lib/spaces'; import { getTestScenarios } from '../../../saved_object_api_integration/common/lib/saved_object_test_utils'; -import { TestUser } from '../../../saved_object_api_integration/common/lib/types'; -import { - getShareableReferencesTestSuiteFactory, +import type { TestUser } from '../../../saved_object_api_integration/common/lib/types'; +import type { FtrProviderContext } from '../../common/ftr_provider_context'; +import { SPACES } from '../../common/lib/spaces'; +import type { GetShareableReferencesTestCase, GetShareableReferencesTestDefinition, - TEST_CASE_OBJECTS, +} from '../../common/suites/get_shareable_references'; +import { EXPECTED_RESULTS, + getShareableReferencesTestSuiteFactory, + TEST_CASE_OBJECTS, } from '../../common/suites/get_shareable_references'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; const { DEFAULT: { spaceId: DEFAULT_SPACE_ID }, diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts index ae5f7b48a2809..756d47308f7fc 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts @@ -5,8 +5,8 @@ * 2.0. */ +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { createUsersAndRoles } from '../../common/lib/create_users_and_roles'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function ({ loadTestFile, getService }: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/resolve_copy_to_space_conflicts.ts index 2f1788ae348f9..da5054436dc69 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/resolve_copy_to_space_conflicts.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/resolve_copy_to_space_conflicts.ts @@ -5,10 +5,10 @@ * 2.0. */ +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { AUTHENTICATION } from '../../common/lib/authentication'; import { SPACES } from '../../common/lib/spaces'; import { resolveCopyToSpaceConflictsSuite } from '../../common/suites/resolve_copy_to_space_conflicts'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function resolveCopyToSpaceConflictsTestSuite(context: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/update.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/update.ts index 40ddc6549edcc..57fd1c5b1a202 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/update.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/update.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { SuperTest } from 'supertest'; +import type { SuperTest } from 'supertest'; + +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { AUTHENTICATION } from '../../common/lib/authentication'; import { SPACES } from '../../common/lib/spaces'; import { updateTestSuiteFactory } from '../../common/suites/update'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function updateSpaceTestSuite({ getService }: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/update_objects_spaces.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/update_objects_spaces.ts index c6a97337e6ad9..ab7e4db5908f6 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/update_objects_spaces.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/update_objects_spaces.ts @@ -5,19 +5,19 @@ * 2.0. */ -import { SPACES } from '../../common/lib/spaces'; import { - testCaseFailures, getTestScenarios, + testCaseFailures, } from '../../../saved_object_api_integration/common/lib/saved_object_test_utils'; -import { TestUser } from '../../../saved_object_api_integration/common/lib/types'; +import type { TestUser } from '../../../saved_object_api_integration/common/lib/types'; +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { MULTI_NAMESPACE_SAVED_OBJECT_TEST_CASES as CASES } from '../../common/lib/saved_object_test_cases'; -import { - updateObjectsSpacesTestSuiteFactory, - UpdateObjectsSpacesTestDefinition, +import { SPACES } from '../../common/lib/spaces'; +import type { UpdateObjectsSpacesTestCase, + UpdateObjectsSpacesTestDefinition, } from '../../common/suites/update_objects_spaces'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { updateObjectsSpacesTestSuiteFactory } from '../../common/suites/update_objects_spaces'; const { DEFAULT: { spaceId: DEFAULT_SPACE_ID }, diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/copy_to_space.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/copy_to_space.ts index 4139e94610f08..3867d528ef374 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/copy_to_space.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/copy_to_space.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { copyToSpaceTestSuiteFactory } from '../../common/suites/copy_to_space'; // eslint-disable-next-line import/no-default-export diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/create.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/create.ts index eea6204267bdd..c98754d60eec3 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/create.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/create.ts @@ -5,10 +5,11 @@ * 2.0. */ -import { SuperTest } from 'supertest'; +import type { SuperTest } from 'supertest'; + +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { SPACES } from '../../common/lib/spaces'; import { createTestSuiteFactory } from '../../common/suites/create'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createSpacesOnlySuite({ getService }: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/delete.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/delete.ts index f066018744818..da50b9c37f190 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/delete.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/delete.ts @@ -5,10 +5,11 @@ * 2.0. */ -import { SuperTest } from 'supertest'; +import type { SuperTest } from 'supertest'; + +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { SPACES } from '../../common/lib/spaces'; import { deleteTestSuiteFactory } from '../../common/suites/delete'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function deleteSpaceTestSuite({ getService }: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/disable_legacy_url_aliases.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/disable_legacy_url_aliases.ts index 32e774e2de636..1818beef05118 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/disable_legacy_url_aliases.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/disable_legacy_url_aliases.ts @@ -5,14 +5,14 @@ * 2.0. */ +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { SPACES } from '../../common/lib/spaces'; +import type { DisableLegacyUrlAliasesTestCase } from '../../common/suites/disable_legacy_url_aliases'; import { disableLegacyUrlAliasesTestSuiteFactory, - DisableLegacyUrlAliasesTestCase, - TEST_CASE_TARGET_TYPE, TEST_CASE_SOURCE_ID, + TEST_CASE_TARGET_TYPE, } from '../../common/suites/disable_legacy_url_aliases'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; const { DEFAULT: { spaceId: DEFAULT_SPACE_ID }, diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/get.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/get.ts index e7f9acc06b655..5cca880f2a7f4 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/get.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/get.ts @@ -5,10 +5,11 @@ * 2.0. */ -import { SuperTest } from 'supertest'; +import type { SuperTest } from 'supertest'; + +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { SPACES } from '../../common/lib/spaces'; import { getTestSuiteFactory } from '../../common/suites/get'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function getSpaceTestSuite({ getService }: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/get_all.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/get_all.ts index 6331c843649fa..4b52f25eecbea 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/get_all.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/get_all.ts @@ -5,10 +5,11 @@ * 2.0. */ -import { SuperTest } from 'supertest'; +import type { SuperTest } from 'supertest'; + +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { SPACES } from '../../common/lib/spaces'; import { getAllTestSuiteFactory } from '../../common/suites/get_all'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function getAllSpacesTestSuite({ getService }: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/get_shareable_references.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/get_shareable_references.ts index 5eec1dda83e5a..15fef24ad0d69 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/get_shareable_references.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/get_shareable_references.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { SPACES } from '../../common/lib/spaces'; import { getTestScenarios } from '../../../saved_object_api_integration/common/lib/saved_object_test_utils'; +import type { FtrProviderContext } from '../../common/ftr_provider_context'; +import { SPACES } from '../../common/lib/spaces'; +import type { GetShareableReferencesTestCase } from '../../common/suites/get_shareable_references'; import { + EXPECTED_RESULTS, getShareableReferencesTestSuiteFactory, - GetShareableReferencesTestCase, TEST_CASE_OBJECTS, - EXPECTED_RESULTS, } from '../../common/suites/get_shareable_references'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; const { DEFAULT: { spaceId: DEFAULT_SPACE_ID }, diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/index.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/index.ts index 50c97b9ca0a75..433ce2c6c444c 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/index.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function spacesOnlyTestSuite({ loadTestFile }: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/resolve_copy_to_space_conflicts.ts index 2248c67cd8219..9fc16ed64abb8 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/resolve_copy_to_space_conflicts.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/resolve_copy_to_space_conflicts.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { resolveCopyToSpaceConflictsSuite } from '../../common/suites/resolve_copy_to_space_conflicts'; // eslint-disable-next-line import/no-default-export diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/update.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/update.ts index 40e4df87e74e9..4a4055896e546 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/update.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/update.ts @@ -5,10 +5,11 @@ * 2.0. */ -import { SuperTest } from 'supertest'; +import type { SuperTest } from 'supertest'; + +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { SPACES } from '../../common/lib/spaces'; import { updateTestSuiteFactory } from '../../common/suites/update'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function updateSpaceTestSuite({ getService }: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/update_objects_spaces.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/update_objects_spaces.ts index a8c2bdce2a3a5..c45aa9edc6741 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/update_objects_spaces.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/update_objects_spaces.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { SPACES } from '../../common/lib/spaces'; import { - testCaseFailures, getTestScenarios, + testCaseFailures, } from '../../../saved_object_api_integration/common/lib/saved_object_test_utils'; +import type { FtrProviderContext } from '../../common/ftr_provider_context'; import { MULTI_NAMESPACE_SAVED_OBJECT_TEST_CASES as CASES } from '../../common/lib/saved_object_test_cases'; +import { SPACES } from '../../common/lib/spaces'; import type { UpdateObjectsSpacesTestCase } from '../../common/suites/update_objects_spaces'; import { updateObjectsSpacesTestSuiteFactory } from '../../common/suites/update_objects_spaces'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; const { DEFAULT: { spaceId: DEFAULT_SPACE_ID }, diff --git a/x-pack/test/spaces_api_integration/spaces_only/telemetry/index.ts b/x-pack/test/spaces_api_integration/spaces_only/telemetry/index.ts index d2cd3fb8a4d6e..3b7f3e3232b99 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/telemetry/index.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/telemetry/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function spacesOnlyTestSuite({ loadTestFile }: FtrProviderContext) { diff --git a/x-pack/test/spaces_api_integration/spaces_only/telemetry/telemetry.ts b/x-pack/test/spaces_api_integration/spaces_only/telemetry/telemetry.ts index 98b3c88354593..a2b73f597414a 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/telemetry/telemetry.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/telemetry/telemetry.ts @@ -6,7 +6,8 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; + +import type { FtrProviderContext } from '../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authentication.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authentication.ts index 7f31db43a3f00..644f0c5b852a7 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authentication.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authentication.ts @@ -6,51 +6,58 @@ */ import expect from 'expect'; +import { SupertestWithRoleScopeType } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { RoleCredentials } from '../../../../shared/services'; export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const config = getService('config'); - + const roleScopedSupertest = getService('roleScopedSupertest'); const svlCommonApi = getService('svlCommonApi'); - const svlUserManager = getService('svlUserManager'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - let roleAuthc: RoleCredentials; + let supertestAdminWithApiKey: SupertestWithRoleScopeType; + let supertestViewerWithApiKey: SupertestWithRoleScopeType; + let supertestViewerWithCookieCredentials: SupertestWithRoleScopeType; + describe('security/authentication', function () { before(async () => { - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); + supertestAdminWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('admin'); + supertestViewerWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('viewer'); + supertestViewerWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope( + 'viewer', + { + useCookieHeader: true, + withCommonHeaders: true, + } + ); }); after(async () => { - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); + await supertestAdminWithApiKey.destroy(); + await supertestViewerWithApiKey.destroy(); + await supertestViewerWithCookieCredentials.destroy(); }); describe('route access', () => { describe('disabled', () => { // ToDo: uncomment when we disable login // it('login', async () => { - // const { body, status } = await supertestWithoutAuth - // .post('/internal/security/login') - // .set(svlCommonApi.getInternalRequestHeader()).set(roleAuthc.apiKeyHeader) + // const { body, status } = await supertestAdminWithApiKey + // .post('/internal/security/login'); // svlCommonApi.assertApiNotFound(body, status); // }); it('logout (deprecated)', async () => { - const { body, status } = await supertestWithoutAuth + const { body, status } = await supertestAdminWithApiKey .get('/api/security/v1/logout') - .set(svlCommonApi.getInternalRequestHeader()) - .set(roleAuthc.apiKeyHeader); + .set(svlCommonApi.getInternalRequestHeader()); svlCommonApi.assertApiNotFound(body, status); }); it('get current user (deprecated)', async () => { - const { body, status } = await supertest + const { body, status } = await supertestAdminWithApiKey .get('/internal/security/v1/me') .set(svlCommonApi.getInternalRequestHeader()); svlCommonApi.assertApiNotFound(body, status); }); it('acknowledge access agreement', async () => { - const { body, status } = await supertest + const { body, status } = await supertestAdminWithApiKey .post('/internal/security/access_agreement/acknowledge') .set(svlCommonApi.getInternalRequestHeader()); svlCommonApi.assertApiNotFound(body, status); @@ -58,56 +65,56 @@ export default function ({ getService }: FtrProviderContext) { describe('OIDC', () => { it('OIDC implicit', async () => { - const { body, status } = await supertest + const { body, status } = await supertestAdminWithApiKey .get('/api/security/oidc/implicit') .set(svlCommonApi.getInternalRequestHeader()); svlCommonApi.assertApiNotFound(body, status); }); it('OIDC implicit (deprecated)', async () => { - const { body, status } = await supertest + const { body, status } = await supertestAdminWithApiKey .get('/api/security/v1/oidc/implicit') .set(svlCommonApi.getInternalRequestHeader()); svlCommonApi.assertApiNotFound(body, status); }); it('OIDC implicit.js', async () => { - const { body, status } = await supertest + const { body, status } = await supertestAdminWithApiKey .get('/internal/security/oidc/implicit.js') .set(svlCommonApi.getInternalRequestHeader()); svlCommonApi.assertApiNotFound(body, status); }); it('OIDC callback', async () => { - const { body, status } = await supertest + const { body, status } = await supertestAdminWithApiKey .get('/api/security/oidc/callback') .set(svlCommonApi.getInternalRequestHeader()); svlCommonApi.assertApiNotFound(body, status); }); it('OIDC callback (deprecated)', async () => { - const { body, status } = await supertest + const { body, status } = await supertestAdminWithApiKey .get('/api/security/v1/oidc') .set(svlCommonApi.getInternalRequestHeader()); svlCommonApi.assertApiNotFound(body, status); }); it('OIDC login', async () => { - const { body, status } = await supertest + const { body, status } = await supertestAdminWithApiKey .post('/api/security/oidc/initiate_login') .set(svlCommonApi.getInternalRequestHeader()); svlCommonApi.assertApiNotFound(body, status); }); it('OIDC login (deprecated)', async () => { - const { body, status } = await supertest + const { body, status } = await supertestAdminWithApiKey .post('/api/security/v1/oidc') .set(svlCommonApi.getInternalRequestHeader()); svlCommonApi.assertApiNotFound(body, status); }); it('OIDC 3rd party login', async () => { - const { body, status } = await supertest + const { body, status } = await supertestAdminWithApiKey .get('/api/security/oidc/initiate_login') .set(svlCommonApi.getInternalRequestHeader()); svlCommonApi.assertApiNotFound(body, status); @@ -115,7 +122,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('SAML callback (deprecated)', async () => { - const { body, status } = await supertest + const { body, status } = await supertestAdminWithApiKey .post('/api/security/v1/saml') .set(svlCommonApi.getInternalRequestHeader()); svlCommonApi.assertApiNotFound(body, status); @@ -127,9 +134,9 @@ export default function ({ getService }: FtrProviderContext) { let body: any; let status: number; - ({ body, status } = await supertest - .get('/internal/security/me') - .set(svlCommonApi.getCommonRequestHeader())); + ({ body, status } = await supertestViewerWithCookieCredentials.get( + '/internal/security/me' + )); // expect a rejection because we're not using the internal header expect(body).toEqual({ statusCode: 400, @@ -140,24 +147,22 @@ export default function ({ getService }: FtrProviderContext) { }); expect(status).toBe(400); - ({ body, status } = await supertest + ({ body, status } = await supertestViewerWithCookieCredentials .get('/internal/security/me') .set(svlCommonApi.getInternalRequestHeader())); // expect success because we're using the internal header - expect(body).toEqual({ - authentication_provider: { name: '__http__', type: 'http' }, - authentication_realm: { name: 'file1', type: 'file' }, - authentication_type: 'realm', - elastic_cloud_user: false, - email: null, - enabled: true, - full_name: null, - lookup_realm: { name: 'file1', type: 'file' }, - metadata: {}, - operator: true, - roles: ['superuser'], - username: config.get('servers.kibana.username'), - }); + expect(body).toEqual( + expect.objectContaining({ + authentication_provider: { name: 'cloud-saml-kibana', type: 'saml' }, + authentication_type: 'token', + authentication_realm: { + name: 'cloud-saml-kibana', + type: 'saml', + }, + enabled: true, + roles: [expect.stringContaining('viewer')], + }) + ); expect(status).toBe(200); }); @@ -166,9 +171,9 @@ export default function ({ getService }: FtrProviderContext) { let body: any; let status: number; - ({ body, status } = await supertest - .post('/internal/security/login') - .set(svlCommonApi.getCommonRequestHeader())); + ({ body, status } = await supertestViewerWithCookieCredentials.post( + '/internal/security/login' + )); // expect a rejection because we're not using the internal header expect(body).toEqual({ statusCode: 400, @@ -179,7 +184,7 @@ export default function ({ getService }: FtrProviderContext) { }); expect(status).toBe(400); - ({ body, status } = await supertest + ({ body, status } = await supertestViewerWithCookieCredentials .post('/internal/security/login') .set(svlCommonApi.getInternalRequestHeader())); expect(status).not.toBe(404); @@ -188,12 +193,12 @@ export default function ({ getService }: FtrProviderContext) { describe('public', () => { it('logout', async () => { - const { status } = await supertest.get('/api/security/logout'); + const { status } = await supertestViewerWithApiKey.get('/api/security/logout'); expect(status).toBe(302); }); it('SAML callback', async () => { - const { body, status } = await supertest + const { body, status } = await supertestViewerWithApiKey .post('/api/security/saml/callback') .set(svlCommonApi.getCommonRequestHeader()) .send({ diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authorization.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authorization.ts index bc01b14848eff..bd706132d4874 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authorization.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/authorization.ts @@ -75,7 +75,7 @@ export default function ({ getService }: FtrProviderContext) { it('get role', async () => { const { body, status } = await supertestAdminWithApiKey.get( - '/api/security/role/superuser' + '/api/security/role/someRole' // mame of the role doesn't matter, we're checking the endpoint doesn't exist ); svlCommonApi.assertApiNotFound(body, status); }); @@ -87,7 +87,7 @@ export default function ({ getService }: FtrProviderContext) { it('delete role', async () => { const { body, status } = await supertestAdminWithApiKey.delete( - '/api/security/role/superuser' + '/api/security/role/someRole' // mame of the role doesn't matter, we're checking the endpoint doesn't exist ); svlCommonApi.assertApiNotFound(body, status); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/serverless_metering/cloud_security_metering.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/serverless_metering/cloud_security_metering.ts index b3db98c829afd..f3d613a41d590 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/serverless_metering/cloud_security_metering.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/serverless_metering/cloud_security_metering.ts @@ -10,11 +10,10 @@ import { CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN } from '@kbn/cloud-secu import { LATEST_FINDINGS_INDEX_DEFAULT_NS } from '@kbn/cloud-security-posture-plugin/common/constants'; import * as http from 'http'; import { - deleteIndex, createPackagePolicy, createCloudDefendPackagePolicy, - bulkIndex, } from '@kbn/test-suites-xpack/api_integration/apis/cloud_security_posture/helper'; +import { EsIndexDataProvider } from '@kbn/test-suites-xpack/cloud_security_posture_api/utils'; import { RoleCredentials } from '../../../../../shared/services'; import { getMockFindings, getMockDefendForContainersHeartbeats } from './mock_data'; import type { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -32,6 +31,12 @@ export default function (providerContext: FtrProviderContext) { const svlCommonApi = getService('svlCommonApi'); const svlUserManager = getService('svlUserManager'); const supertestWithoutAuth = getService('supertestWithoutAuth'); + const findingsIndex = new EsIndexDataProvider(es, LATEST_FINDINGS_INDEX_DEFAULT_NS); + const cloudDefinedIndex = new EsIndexDataProvider(es, CLOUD_DEFEND_HEARTBEAT_INDEX_DEFAULT_NS); + const vulnerabilitiesIndex = new EsIndexDataProvider( + es, + CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN + ); /* This test aims to intercept the usage API request sent by the metering background task manager. @@ -67,25 +72,17 @@ export default function (providerContext: FtrProviderContext) { agentPolicyId = agentPolicyResponse.item.id; - await deleteIndex(es, [ - LATEST_FINDINGS_INDEX_DEFAULT_NS, - CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, - CLOUD_DEFEND_HEARTBEAT_INDEX_DEFAULT_NS, - ]); + await findingsIndex.deleteAll(); + await vulnerabilitiesIndex.deleteAll(); + await cloudDefinedIndex.deleteAll(); }); afterEach(async () => { - await deleteIndex(es, [ - LATEST_FINDINGS_INDEX_DEFAULT_NS, - CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, - ]); await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); - await deleteIndex(es, [ - LATEST_FINDINGS_INDEX_DEFAULT_NS, - CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, - CLOUD_DEFEND_HEARTBEAT_INDEX_DEFAULT_NS, - ]); + await findingsIndex.deleteAll(); + await vulnerabilitiesIndex.deleteAll(); + await cloudDefinedIndex.deleteAll(); }); after(async () => { await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); @@ -116,11 +113,7 @@ export default function (providerContext: FtrProviderContext) { numberOfFindings: 10, }); - await bulkIndex( - es, - [...billableFindings, ...notBillableFindings], - LATEST_FINDINGS_INDEX_DEFAULT_NS - ); + await findingsIndex.addBulk([...billableFindings, ...notBillableFindings]); let interceptedRequestBody: UsageRecord[] = []; await retry.try(async () => { @@ -160,11 +153,7 @@ export default function (providerContext: FtrProviderContext) { numberOfFindings: 11, }); - await bulkIndex( - es, - [...billableFindings, ...notBillableFindings], - LATEST_FINDINGS_INDEX_DEFAULT_NS - ); + await findingsIndex.addBulk([...billableFindings, ...notBillableFindings]); let interceptedRequestBody: UsageRecord[] = []; @@ -199,7 +188,7 @@ export default function (providerContext: FtrProviderContext) { numberOfFindings: 2, }); - await bulkIndex(es, billableFindings, CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN); + await vulnerabilitiesIndex.addBulk(billableFindings); let interceptedRequestBody: UsageRecord[] = []; @@ -233,11 +222,11 @@ export default function (providerContext: FtrProviderContext) { isBlockActionEnables: false, numberOfHearbeats: 2, }); - await bulkIndex( - es, - [...blockActionEnabledHeartbeats, ...blockActionDisabledHeartbeats], - CLOUD_DEFEND_HEARTBEAT_INDEX_DEFAULT_NS - ); + + await cloudDefinedIndex.addBulk([ + ...blockActionEnabledHeartbeats, + ...blockActionDisabledHeartbeats, + ]); let interceptedRequestBody: UsageRecord[] = []; @@ -315,22 +304,17 @@ export default function (providerContext: FtrProviderContext) { }); await Promise.all([ - bulkIndex( - es, - [ - ...billableFindingsCSPM, - ...notBillableFindingsCSPM, - ...billableFindingsKSPM, - ...notBillableFindingsKSPM, - ], - LATEST_FINDINGS_INDEX_DEFAULT_NS - ), - bulkIndex(es, [...billableFindingsCNVM], CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN), - bulkIndex( - es, - [...blockActionEnabledHeartbeats, ...blockActionDisabledHeartbeats], - CLOUD_DEFEND_HEARTBEAT_INDEX_DEFAULT_NS - ), + findingsIndex.addBulk([ + ...billableFindingsCSPM, + ...notBillableFindingsCSPM, + ...billableFindingsKSPM, + ...notBillableFindingsKSPM, + ]), + vulnerabilitiesIndex.addBulk([...billableFindingsCNVM]), + cloudDefinedIndex.addBulk([ + ...blockActionEnabledHeartbeats, + ...blockActionDisabledHeartbeats, + ]), ]); // Intercept and verify usage API request diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/serverless_metering/mock_data.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/serverless_metering/mock_data.ts index 5e5844eaaf3b5..1991b53b85b35 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/serverless_metering/mock_data.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/serverless_metering/mock_data.ts @@ -82,6 +82,8 @@ const mockFiniding = (postureType: string, isBillableAsset?: boolean) => { }, }; } + + throw new Error('Invalid posture type'); }; export const getMockDefendForContainersHeartbeats = ({ diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexed.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexed.ts index a9da3a42cdfc8..b53163796a6ee 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexed.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexed.ts @@ -8,16 +8,9 @@ import expect from '@kbn/expect'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { CspSetupStatus } from '@kbn/cloud-security-posture-common'; import { CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN } from '@kbn/cloud-security-posture-common'; -import { - FINDINGS_INDEX_DEFAULT_NS, - LATEST_FINDINGS_INDEX_DEFAULT_NS, - VULNERABILITIES_INDEX_DEFAULT_NS, -} from '@kbn/cloud-security-posture-plugin/common/constants'; -import { - deleteIndex, - addIndex, - createPackagePolicy, -} from '@kbn/test-suites-xpack/api_integration/apis/cloud_security_posture/helper'; +import { LATEST_FINDINGS_INDEX_DEFAULT_NS } from '@kbn/cloud-security-posture-plugin/common/constants'; +import { createPackagePolicy } from '@kbn/test-suites-xpack/api_integration/apis/cloud_security_posture/helper'; +import { EsIndexDataProvider } from '@kbn/test-suites-xpack/cloud_security_posture_api/utils'; import { findingsMockData, vulnerabilityMockData, @@ -25,13 +18,6 @@ import { import { FtrProviderContext } from '../../../../ftr_provider_context'; import { RoleCredentials } from '../../../../../shared/services'; -const INDEX_ARRAY = [ - FINDINGS_INDEX_DEFAULT_NS, - LATEST_FINDINGS_INDEX_DEFAULT_NS, - CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, - VULNERABILITIES_INDEX_DEFAULT_NS, -]; - export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const es = getService('es'); @@ -40,6 +26,11 @@ export default function (providerContext: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); const svlCommonApi = getService('svlCommonApi'); const svlUserManager = getService('svlUserManager'); + const latestFindingsIndex = new EsIndexDataProvider(es, LATEST_FINDINGS_INDEX_DEFAULT_NS); + const latestVulnerabilitiesIndex = new EsIndexDataProvider( + es, + CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN + ); describe('GET /internal/cloud_security_posture/status', function () { // security_exception: action [indices:admin/create] is unauthorized for user [elastic] with effective roles [superuser] on restricted indices [.fleet-actions-7], this action is granted by the index privileges [create_index,manage,all] @@ -74,13 +65,13 @@ export default function (providerContext: FtrProviderContext) { agentPolicyId = agentPolicyResponse.item.id; - await deleteIndex(es, INDEX_ARRAY); - await addIndex(es, findingsMockData, LATEST_FINDINGS_INDEX_DEFAULT_NS); - await addIndex(es, vulnerabilityMockData, CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN); + await latestFindingsIndex.deleteAll(); + await latestVulnerabilitiesIndex.deleteAll(); }); afterEach(async () => { - await deleteIndex(es, INDEX_ARRAY); + await latestFindingsIndex.deleteAll(); + await latestVulnerabilitiesIndex.deleteAll(); await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); }); @@ -98,6 +89,8 @@ export default function (providerContext: FtrProviderContext) { internalRequestHeader ); + await latestFindingsIndex.addBulk(findingsMockData); + const { body: res }: { body: CspSetupStatus } = await supertestWithoutAuth .get(`/internal/cloud_security_posture/status`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') @@ -124,6 +117,8 @@ export default function (providerContext: FtrProviderContext) { internalRequestHeader ); + await latestFindingsIndex.addBulk(findingsMockData); + const { body: res }: { body: CspSetupStatus } = await supertestWithoutAuth .get(`/internal/cloud_security_posture/status`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') @@ -150,6 +145,8 @@ export default function (providerContext: FtrProviderContext) { internalRequestHeader ); + await latestVulnerabilitiesIndex.addBulk(vulnerabilityMockData); + const { body: res }: { body: CspSetupStatus } = await supertestWithoutAuth .get(`/internal/cloud_security_posture/status`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexing.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexing.ts index ec6a5835e6aa3..e531f2a5cc14e 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexing.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/status/status_indexing.ts @@ -7,31 +7,19 @@ import expect from '@kbn/expect'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { CspSetupStatus } from '@kbn/cloud-security-posture-common'; -import { CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN } from '@kbn/cloud-security-posture-common'; import { FINDINGS_INDEX_DEFAULT_NS, - LATEST_FINDINGS_INDEX_DEFAULT_NS, VULNERABILITIES_INDEX_DEFAULT_NS, } from '@kbn/cloud-security-posture-plugin/common/constants'; -import { - deleteIndex, - addIndex, - createPackagePolicy, -} from '@kbn/test-suites-xpack/api_integration/apis/cloud_security_posture/helper'; +import { createPackagePolicy } from '@kbn/test-suites-xpack/api_integration/apis/cloud_security_posture/helper'; import { findingsMockData, vulnerabilityMockData, } from '@kbn/test-suites-xpack/api_integration/apis/cloud_security_posture/mock_data'; +import { EsIndexDataProvider } from '@kbn/test-suites-xpack/cloud_security_posture_api/utils'; import { FtrProviderContext } from '../../../../ftr_provider_context'; import { RoleCredentials } from '../../../../../shared/services'; -const INDEX_ARRAY = [ - FINDINGS_INDEX_DEFAULT_NS, - LATEST_FINDINGS_INDEX_DEFAULT_NS, - CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, - VULNERABILITIES_INDEX_DEFAULT_NS, -]; - export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const es = getService('es'); @@ -40,6 +28,8 @@ export default function (providerContext: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); const svlCommonApi = getService('svlCommonApi'); const svlUserManager = getService('svlUserManager'); + const findingsIndex = new EsIndexDataProvider(es, FINDINGS_INDEX_DEFAULT_NS); + const vulnerabilitiesIndex = new EsIndexDataProvider(es, VULNERABILITIES_INDEX_DEFAULT_NS); describe('GET /internal/cloud_security_posture/status', function () { // security_exception: action [indices:admin/create] is unauthorized for user [elastic] with effective roles [superuser] on restricted indices [.fleet-actions-7], this action is granted by the index privileges [create_index,manage,all] @@ -73,13 +63,13 @@ export default function (providerContext: FtrProviderContext) { }); agentPolicyId = agentPolicyResponse.item.id; - await deleteIndex(es, INDEX_ARRAY); - await addIndex(es, findingsMockData, FINDINGS_INDEX_DEFAULT_NS); - await addIndex(es, vulnerabilityMockData, VULNERABILITIES_INDEX_DEFAULT_NS); + await findingsIndex.deleteAll(); + await vulnerabilitiesIndex.deleteAll(); }); afterEach(async () => { - await deleteIndex(es, INDEX_ARRAY); + await findingsIndex.deleteAll(); + await vulnerabilitiesIndex.deleteAll(); await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); }); @@ -97,6 +87,8 @@ export default function (providerContext: FtrProviderContext) { internalRequestHeader ); + await findingsIndex.addBulk(findingsMockData); + const { body: res }: { body: CspSetupStatus } = await supertestWithoutAuth .get(`/internal/cloud_security_posture/status`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') @@ -123,6 +115,8 @@ export default function (providerContext: FtrProviderContext) { internalRequestHeader ); + await findingsIndex.addBulk(findingsMockData); + const { body: res }: { body: CspSetupStatus } = await supertestWithoutAuth .get(`/internal/cloud_security_posture/status`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') @@ -149,6 +143,8 @@ export default function (providerContext: FtrProviderContext) { internalRequestHeader ); + await vulnerabilitiesIndex.addBulk(vulnerabilityMockData); + const { body: res }: { body: CspSetupStatus } = await supertestWithoutAuth .get(`/internal/cloud_security_posture/status`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/telemetry.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/telemetry.ts index 62cf85b47d997..15700419a7e96 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/telemetry.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/telemetry.ts @@ -7,11 +7,12 @@ import expect from '@kbn/expect'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; -import { - data as telemetryMockData, - MockTelemetryFindings, -} from '@kbn/test-suites-xpack/cloud_security_posture_api/telemetry/data'; +import { data as telemetryMockData } from '@kbn/test-suites-xpack/cloud_security_posture_api/telemetry/data'; import { createPackagePolicy } from '@kbn/test-suites-xpack/api_integration/apis/cloud_security_posture/helper'; +import { + waitForPluginInitialized, + EsIndexDataProvider, +} from '@kbn/test-suites-xpack/cloud_security_posture_api/utils'; import { SupertestWithRoleScopeType } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services'; import type { FtrProviderContext } from '../../../ftr_provider_context'; import { RoleCredentials } from '../../../../shared/services'; @@ -21,7 +22,7 @@ const FINDINGS_INDEX = 'logs-cloud_security_posture.findings_latest-default'; export default function ({ getService }: FtrProviderContext) { const retry = getService('retry'); const es = getService('es'); - const log = getService('log'); + const logger = getService('log'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const supertestWithoutAuth = getService('supertestWithoutAuth'); @@ -33,24 +34,7 @@ export default function ({ getService }: FtrProviderContext) { let roleAuthc: RoleCredentials; let internalRequestHeader: { 'x-elastic-internal-origin': string; 'kbn-xsrf': string }; - const index = { - remove: () => - es.deleteByQuery({ - index: FINDINGS_INDEX, - query: { match_all: {} }, - refresh: true, - }), - - add: async (mockTelemetryFindings: MockTelemetryFindings[]) => { - const operations = mockTelemetryFindings.flatMap((doc) => [ - { index: { _index: FINDINGS_INDEX } }, - doc, - ]); - - const response = await es.bulk({ refresh: 'wait_for', index: FINDINGS_INDEX, operations }); - expect(response.errors).to.eql(false); - }, - }; + const findingsIndex = new EsIndexDataProvider(es, FINDINGS_INDEX); describe('Verify cloud_security_posture telemetry payloads', function () { // security_exception: action [indices:admin/create] is unauthorized for user [elastic] with effective roles [superuser] on restricted indices [.fleet-actions-7], this action is granted by the index privileges [create_index,manage,all] @@ -95,22 +79,11 @@ export default function ({ getService }: FtrProviderContext) { internalRequestHeader ); - log.debug('Check CSP plugin is initialized'); - await retry.try(async () => { - const supertestAdminWithHttpHeaderV1 = await roleScopedSupertest.getSupertestWithRoleScope( - 'admin', - { - useCookieHeader: true, - withInternalHeaders: true, - withCustomHeaders: { [ELASTIC_HTTP_VERSION_HEADER]: '1' }, - } - ); - const response = await supertestAdminWithHttpHeaderV1 - .get('/internal/cloud_security_posture/status?check=init') - .expect(200); - expect(response.body).to.eql({ isPluginInitialized: true }); - log.debug('CSP plugin is initialized'); + const supertestAdmin = await roleScopedSupertest.getSupertestWithRoleScope('admin', { + useCookieHeader: true, + withInternalHeaders: true, }); + await waitForPluginInitialized({ logger, retry, supertest: supertestAdmin }); }); after(async () => { @@ -120,11 +93,11 @@ export default function ({ getService }: FtrProviderContext) { }); afterEach(async () => { - await index.remove(); + await findingsIndex.deleteAll(); }); it('includes only KSPM findings', async () => { - await index.add(telemetryMockData.kspmFindings); + await findingsIndex.addBulk(telemetryMockData.kspmFindings); const { body: [{ stats: apiResponse }], @@ -175,7 +148,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('includes only CSPM findings', async () => { - await index.add(telemetryMockData.cspmFindings); + await findingsIndex.addBulk(telemetryMockData.cspmFindings); const { body: [{ stats: apiResponse }], @@ -218,8 +191,10 @@ export default function ({ getService }: FtrProviderContext) { }); it('includes CSPM and KSPM findings', async () => { - await index.add(telemetryMockData.kspmFindings); - await index.add(telemetryMockData.cspmFindings); + await findingsIndex.addBulk([ + ...telemetryMockData.kspmFindings, + ...telemetryMockData.cspmFindings, + ]); const { body: [{ stats: apiResponse }], @@ -294,7 +269,7 @@ export default function ({ getService }: FtrProviderContext) { }); it(`'includes only KSPM findings without posture_type'`, async () => { - await index.add(telemetryMockData.kspmFindingsNoPostureType); + await findingsIndex.addBulk(telemetryMockData.kspmFindingsNoPostureType); const { body: [{ stats: apiResponse }], @@ -346,8 +321,10 @@ export default function ({ getService }: FtrProviderContext) { }); it('includes KSPM findings without posture_type and CSPM findings as well', async () => { - await index.add(telemetryMockData.kspmFindingsNoPostureType); - await index.add(telemetryMockData.cspmFindings); + await findingsIndex.addBulk([ + ...telemetryMockData.kspmFindingsNoPostureType, + ...telemetryMockData.cspmFindings, + ]); const { body: [{ stats: apiResponse }], diff --git a/x-pack/test_serverless/api_integration/test_suites/security/config.ts b/x-pack/test_serverless/api_integration/test_suites/security/config.ts index d40cde3c25837..0b24438b81591 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/config.ts @@ -24,6 +24,6 @@ export default createTestConfig({ // useful for testing (also enabled in MKI QA) '--coreApp.allowDynamicConfigOverrides=true', `--xpack.securitySolutionServerless.cloudSecurityUsageReportingTaskInterval=5s`, - `--xpack.securitySolutionServerless.usageApi.url=http://localhost:8081/api/v1/usage`, + `--xpack.securitySolutionServerless.usageApi.url=http://localhost:8081`, ], }); diff --git a/yarn.lock b/yarn.lock index 54a38b2c0e5d3..abc5b5ee2874d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3295,10 +3295,6 @@ version "0.0.0" uid "" -"@kbn/ace@link:packages/kbn-ace": - version "0.0.0" - uid "" - "@kbn/actions-plugin@link:x-pack/plugins/actions": version "0.0.0" uid "" @@ -3319,6 +3315,10 @@ version "0.0.0" uid "" +"@kbn/ai-assistant@link:x-pack/packages/kbn-ai-assistant": + version "0.0.0" + uid "" + "@kbn/aiops-change-point-detection@link:x-pack/packages/ml/aiops_change_point_detection": version "0.0.0" uid "" @@ -5879,6 +5879,10 @@ version "0.0.0" uid "" +"@kbn/observability-logs-overview@link:x-pack/packages/observability/logs_overview": + version "0.0.0" + uid "" + "@kbn/observability-onboarding-e2e@link:x-pack/plugins/observability_solution/observability_onboarding/e2e": version "0.0.0" uid "" @@ -12105,6 +12109,14 @@ use-isomorphic-layout-effect "^1.1.2" use-sync-external-store "^1.0.0" +"@xstate5/react@npm:@xstate/react@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@xstate/react/-/react-4.1.2.tgz#4bfcdf2d9e9ef1eaea7388d1896649345e6679cd" + integrity sha512-orAidFrKCrU0ZwN5l/ABPlBfW2ziRDT2RrYoktRlZ0WRoLvA2E/uAC1JpZt43mCLtc8jrdwYCgJiqx1V8NvGTw== + dependencies: + use-isomorphic-layout-effect "^1.1.2" + use-sync-external-store "^1.2.0" + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -13567,11 +13579,6 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -brace@0.11.1, brace@^0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58" - integrity sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg= - braces@^2.3.1: version "2.3.2" resolved "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz" @@ -16314,7 +16321,7 @@ diacritics@^1.3.0: resolved "https://registry.yarnpkg.com/diacritics/-/diacritics-1.3.0.tgz#3efa87323ebb863e6696cebb0082d48ff3d6f7a1" integrity sha1-PvqHMj67hj5mls67AILUj/PW96E= -diff-match-patch@^1.0.0, diff-match-patch@^1.0.4, diff-match-patch@^1.0.5: +diff-match-patch@^1.0.0, diff-match-patch@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37" integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw== @@ -26605,17 +26612,6 @@ re2js@0.4.2: resolved "https://registry.yarnpkg.com/re2js/-/re2js-0.4.2.tgz#e344697e64d128ea65c121d6581e67ee5bfa5feb" integrity sha512-wuv0p0BGbrVIkobV8zh82WjDurXko0QNCgaif6DdRAljgVm2iio4PVYCwjAxGaWen1/QZXWDM67dIslmz7AIbA== -react-ace@^7.0.5: - version "7.0.5" - resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-7.0.5.tgz#798299fd52ddf3a3dcc92afc5865538463544f01" - integrity sha512-3iI+Rg2bZXCn9K984ll2OF4u9SGcJH96Q1KsUgs9v4M2WePS4YeEHfW2nrxuqJrAkE5kZbxaCE79k6kqK0YBjg== - dependencies: - brace "^0.11.1" - diff-match-patch "^1.0.4" - lodash.get "^4.4.2" - lodash.isequal "^4.5.0" - prop-types "^15.7.2" - react-clientside-effect@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz#29f9b14e944a376b03fb650eed2a754dd128ea3a" @@ -32800,6 +32796,11 @@ xpath@^0.0.33: resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.33.tgz#5136b6094227c5df92002e7c3a13516a5074eb07" integrity sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA== +"xstate5@npm:xstate@^5.18.1", xstate@^5.18.1: + version "5.18.1" + resolved "https://registry.yarnpkg.com/xstate/-/xstate-5.18.1.tgz#c4d43ceaba6e6c31705d36bd96e285de4be4f7f4" + integrity sha512-m02IqcCQbaE/kBQLunwub/5i8epvkD2mFutnL17Oeg1eXTShe1sRF4D5mhv1dlaFO4vbW5gRGRhraeAD5c938g== + xstate@^4.38.2: version "4.38.2" resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.38.2.tgz#1b74544fc9c8c6c713ba77f81c6017e65aa89804"