diff --git a/.buildkite/pipelines/security_solution/api_integration.yml b/.buildkite/pipelines/security_solution/api_integration.yml index 474fbc31b0bc3a..046d9fafe38731 100644 --- a/.buildkite/pipelines/security_solution/api_integration.yml +++ b/.buildkite/pipelines/security_solution/api_integration.yml @@ -1,19 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - queue: n2-16-spot - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: 'Upload runtime info' key: upload_runtime_info - depends_on: build_image agents: queue: n2-4-spot timeout_in_minutes: 300 @@ -24,7 +12,6 @@ steps: - group: 'Execute Tests' key: test_execution - depends_on: build_image steps: - label: Running exception_workflows:qa:serverless command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_workflows:qa:serverless diff --git a/.buildkite/pipelines/security_solution/api_integration_serverless_release.yml b/.buildkite/pipelines/security_solution/api_integration_serverless_release.yml index 25c6f0ce06b8ee..f1b9d002209d03 100644 --- a/.buildkite/pipelines/security_solution/api_integration_serverless_release.yml +++ b/.buildkite/pipelines/security_solution/api_integration_serverless_release.yml @@ -1,7 +1,6 @@ steps: - group: 'API Integration Serverless Release Tests' key: test_execution - depends_on: build_image steps: - label: Running integration tests for Serverless Exception Workflows command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_workflows:qa:serverless:release diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_defend_workflows.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_defend_workflows.yml index 11a99f29fbe146..974514f47c101b 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_defend_workflows.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_defend_workflows.yml @@ -1,23 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa - provider: gcp - machineType: n2-standard-16 - preemptible: true - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: "-1" - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: "Upload runtime info" key: upload_runtime_info - depends_on: build_image agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-qa @@ -31,6 +15,5 @@ steps: limit: 1 - command: "echo 'Running the defend worklows tests in this step" - depends_on: build_image key: test_defend_workflows - label: 'Serverless MKI QA Defend Workflows - Security Solution Cypress Tests' + label: "Serverless MKI QA Defend Workflows - Security Solution Cypress Tests" diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_detection_engine.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_detection_engine.yml index ff4eb22fb2e2df..a57659281d731c 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_detection_engine.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_detection_engine.yml @@ -1,23 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa - provider: gcp - machineType: n2-standard-16 - preemptible: true - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: "-1" - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: "Upload runtime info" key: upload_runtime_info - depends_on: build_image agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-qa @@ -33,7 +17,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:detection_engine label: 'Serverless MKI QA Detection Engine - Security Solution Cypress Tests' key: test_detection_engine - depends_on: build_image env: BK_TEST_SUITE_KEY: "serverless-cypress-detection-engine" agents: @@ -53,7 +36,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:detection_engine:exceptions label: 'Serverless MKI QA Detection Engine - Exceptions - Security Solution Cypress Tests' key: test_detection_engine_exceptions - depends_on: build_image env: BK_TEST_SUITE_KEY: "serverless-cypress-detection-engine" agents: diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_entity_analytics.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_entity_analytics.yml index 518ec0268309b7..97947ed98fd24c 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_entity_analytics.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_entity_analytics.yml @@ -1,23 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa - provider: gcp - machineType: n2-standard-16 - preemptible: true - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: "-1" - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: "Upload runtime info" key: upload_runtime_info - depends_on: build_image agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-qa @@ -32,7 +16,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:entity_analytics label: 'Serverless MKI QA Entity Analytics - Security Solution Cypress Tests' - depends_on: build_image key: test_entity_analytics env: BK_TEST_SUITE_KEY: "serverless-cypress-entity-analytics" diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_explore.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_explore.yml index a8ded33bfdd220..95a87db37bb31e 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_explore.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_explore.yml @@ -1,23 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa - provider: gcp - machineType: n2-standard-16 - preemptible: true - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: "-1" - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: "Upload runtime info" key: upload_runtime_info - depends_on: build_image agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-qa @@ -31,7 +15,6 @@ steps: limit: 1 - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:explore - depends_on: build_image key: test_explore label: 'Serverless MKI QA Explore - Security Solution Cypress Tests' env: diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_gen_ai.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_gen_ai.yml index 2b10150dd55ac6..90018caab7024f 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_gen_ai.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_gen_ai.yml @@ -1,23 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa - provider: gcp - machineType: n2-standard-16 - preemptible: true - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: "-1" - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: "Upload runtime info" key: upload_runtime_info - depends_on: build_image agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-qa @@ -31,8 +15,7 @@ steps: limit: 1 - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:ai_assistant - label: 'Serverless MKI QA AI Assistant - Security Solution Cypress Tests' - depends_on: build_image + label: "Serverless MKI QA AI Assistant - Security Solution Cypress Tests" key: test_ai_assistant env: BK_TEST_SUITE_KEY: "serverless-cypress-gen-ai" @@ -47,5 +30,5 @@ steps: parallelism: 1 retry: automatic: - - exit_status: '-1' + - exit_status: "-1" limit: 1 diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_investigations.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_investigations.yml index b7ddac79641ce6..39f4b66d5c6072 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_investigations.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_investigations.yml @@ -1,23 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa - provider: gcp - machineType: n2-standard-16 - preemptible: true - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: "-1" - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: "Upload runtime info" key: upload_runtime_info - depends_on: build_image agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-qa @@ -31,7 +15,6 @@ steps: limit: 1 - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:investigations - depends_on: build_image key: test_investigations label: 'Serverless MKI QA Investigations - Security Solution Cypress Tests' env: diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_rule_management.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_rule_management.yml index 58f10084bdcdaa..933d7b2de0ac50 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_rule_management.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_security_solution_rule_management.yml @@ -1,23 +1,7 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh - label: Build kibana image - key: build_image - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-qa - provider: gcp - machineType: n2-standard-16 - preemptible: true - timeout_in_minutes: 60 - retry: - automatic: - - exit_status: "-1" - limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh label: "Upload runtime info" key: upload_runtime_info - depends_on: build_image agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-qa @@ -32,7 +16,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:rule_management label: 'Serverless MKI QA Rule Management - Security Solution Cypress Tests' - depends_on: build_image key: test_rule_management env: BK_TEST_SUITE_KEY: "serverless-cypress-rule-management" @@ -52,7 +35,6 @@ steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:rule_management:prebuilt_rules label: 'Serverless MKI QA Rule Management - Prebuilt Rules - Security Solution Cypress Tests' - depends_on: build_image key: test_rule_management_prebuilt_rules env: BK_TEST_SUITE_KEY: "serverless-cypress-rule-management" diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh index f87df9cb58baca..8b0ab8dcae33a1 100755 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh @@ -10,57 +10,87 @@ source .buildkite/scripts/common/util.sh buildkite-agent meta-data set "${BUILDKITE_JOB_ID}_is_test_execution_step" "true" -echo "--- Serverless Security Second Quality Gate" +source .buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh + cd x-pack/test/security_solution_api_integration set +e -QA_API_KEY=$(vault_get security-solution-quality-gate qa_api_key) -QA_CONSOLE_URL=$(vault_get security-solution-quality-gate qa_console_url) - # Generate a random 5-digit number random_number=$((10000 + $RANDOM % 90000)) -if [ -z "${KIBANA_MKI_USE_LATEST_COMMIT+x}" ] || [ "$KIBANA_MKI_USE_LATEST_COMMIT" = "0" ]; then - ENVIRONMENT_DETAILS=$(curl --location "$QA_CONSOLE_URL/api/v1/serverless/projects/security" \ - --header "Authorization: ApiKey $QA_API_KEY" \ +PROXY_URL="https://cloud-handler-test-r344edqiza-uc.a.run.app" +# Check the healthcheck of the proxy service +response=$(curl -s -o /dev/null -w "%{http_code}" "$PROXY_URL/healthcheck") +echo "Proxy Healthcheck Response code: $response" + +if [ "$response" -eq 200 ]; then + # Proxy service is up and running. Use the proxy to handle the projects. + CREATE_URL="$PROXY_URL/projects" + RESET_CREDS_URL="$PROXY_URL/projects/{project_id}/_reset-internal-credentials" + DELETE_URL="$PROXY_URL/projects/{project_id}" + AUTH="Basic $(vault_get security-solution-quality-gate-proxy base_64_encoded_auth)" +else + # Proxy service is not available. Use default single org execution mode using cloud QA directly. + CREATE_URL="$QA_CONSOLE_URL/api/v1/serverless/projects/security" + RESET_CREDS_URL="$QA_CONSOLE_URL/api/v1/serverless/projects/security/{project_id}/_reset-internal-credentials" + DELETE_URL="$QA_CONSOLE_URL/api/v1/serverless/projects/security/{project_id}" + AUTH="ApiKey $CLOUD_QA_API_KEY" +fi + + +if [ -z "${KIBANA_MKI_IMAGE_COMMIT+x}" ]; then + # There is no provided commit to be used so running against whatever image + # is already qualified in Cloud QA. + ENVIRONMENT_DETAILS=$(curl --location "$CREATE_URL" \ + --header "Authorization: $AUTH" \ --header 'Content-Type: application/json' \ --data '{ "name": "ftr-integration-tests-'$random_number'", "region_id": "aws-eu-west-1"}' | jq '.') else - KBN_COMMIT_HASH=${BUILDKITE_COMMIT:0:12} - ENVIRONMENT_DETAILS=$(curl --location "$QA_CONSOLE_URL/api/v1/serverless/projects/security" \ - --header "Authorization: ApiKey $QA_API_KEY" \ + # A commit is provided so it will be used to run the tests against this qualified image. + KBN_COMMIT_HASH=${KIBANA_MKI_IMAGE_COMMIT:0:12} + ENVIRONMENT_DETAILS=$(curl --location "$CREATE_URL" \ + --header "Authorization: $AUTH" \ --header 'Content-Type: application/json' \ --data '{ "name": "ftr-integration-tests-'$random_number'", "region_id": "aws-eu-west-1", "overrides": { "kibana": { - "docker_image" : "docker.elastic.co/kibana-ci/kibana-serverless:sec-sol-qg-'$KBN_COMMIT_HASH'" + "docker_image" : "docker.elastic.co/kibana-ci/kibana-serverless:git-'$KBN_COMMIT_HASH'" } } }' | jq '.') fi +if [ "$response" -eq 200 ]; then + # Proxy is up and running so reading the ES and KB endpoints from the proxy response. + ES_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.elasticsearch_endpoint') + KB_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.kibana_endpoint') + ID=$(echo $ENVIRONMENT_DETAILS | jq -r '.project_id') +else + # Proxy is unavailable so reading the ES and KB endpoints from the cloud QA response. + ES_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.endpoints.elasticsearch') + KB_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.endpoints.kibana') + ID=$(echo $ENVIRONMENT_DETAILS | jq -r '.id') +fi NAME=$(echo $ENVIRONMENT_DETAILS | jq -r '.name') -ID=$(echo $ENVIRONMENT_DETAILS | jq -r '.id') -ES_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.endpoints.elasticsearch') -KB_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.endpoints.kibana') # Wait five seconds for the project to appear sleep 5 # Resetting the credentials of the elastic user in the project -CREDS_BODY=$(curl -s --location --request POST "$QA_CONSOLE_URL/api/v1/serverless/projects/security/$ID/_reset-internal-credentials" \ - --header "Authorization: ApiKey $QA_API_KEY" \ +RESET_CREDENTIALS_URL=$(echo "$RESET_CREDS_URL" | sed "s/{project_id}/$ID/g") +CREDS_BODY=$(curl -s --location --request POST "$RESET_CREDENTIALS_URL" \ + --header "Authorization: $AUTH" \ --header 'Content-Type: application/json' | jq '.') USERNAME=$(echo $CREDS_BODY | jq -r '.username') PASSWORD=$(echo $CREDS_BODY | jq -r '.password') -AUTH=$(echo "$USERNAME:$PASSWORD") +PROJECT_AUTH=$(echo "$USERNAME:$PASSWORD") # Checking if Elasticsearch has status green while : ; do - STATUS=$(curl -u $AUTH --location "$ES_URL:443/_cluster/health?wait_for_status=green&timeout=50s" | jq -r '.status') + STATUS=$(curl -u $PROJECT_AUTH --location "$ES_URL:443/_cluster/health?wait_for_status=green&timeout=50s" | jq -r '.status') if [ "$STATUS" != "green" ]; then echo "Sleeping for 40s to wait for ES status to be green..." sleep 40 @@ -72,7 +102,7 @@ done # Checking if Kibana is available while : ; do - STATUS=$(curl -u $AUTH --location "$KB_URL:443/api/status" | jq -r '.status.overall.level') + STATUS=$(curl -u $PROJECT_AUTH --location "$KB_URL:443/api/status" | jq -r '.status.overall.level') if [ "$STATUS" != "available" ]; then echo "Sleeping for 15s to wait for Kibana to be available..." sleep 15 @@ -90,11 +120,13 @@ FORMATTED_KB_URL="${KB_URL/https:\/\//}" # This is used in order to wait for the environment to be ready. sleep 150 +echo "--- Triggering API tests for $1" TEST_CLOUD=1 TEST_ES_URL="https://$USERNAME:$PASSWORD@$FORMATTED_ES_URL:443" TEST_KIBANA_URL="https://$USERNAME:$PASSWORD@$FORMATTED_KB_URL:443" yarn run $1 cmd_status=$? echo "Exit code with status: $cmd_status" -curl --location --request DELETE "$QA_CONSOLE_URL/api/v1/serverless/projects/security/$ID" \ - --header "Authorization: ApiKey $QA_API_KEY" +DELETE_PROJECT_URL=$(echo "$DELETE_URL" | sed "s/{project_id}/$ID/g") +curl --location --request DELETE "$DELETE_PROJECT_URL" \ + --header "Authorization: $AUTH" exit $cmd_status diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh new file mode 100644 index 00000000000000..85f361752f6f64 --- /dev/null +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +mkdir .ftr + +# The role-users file that is used as a fallback if the proxy service is unavailable. +vault_get security-quality-gate/role-users data -format=json > .ftr/role_users.json +# The role-users files relevant to the proxy service and its orgs. +vault_get security-quality-gate/role-users/sec-sol-auto-01 data -format=json > .ftr/sec-sol-auto-01.json +vault_get security-quality-gate/role-users/sec-sol-auto-02 data -format=json > .ftr/sec-sol-auto-02.json +vault_get security-quality-gate/role-users/sec-sol-auto-03 data -format=json > .ftr/sec-sol-auto-03.json + +# The vault entries relevant to QA Cloud +export CLOUD_QA_API_KEY=$(vault_get security-solution-quality-gate qa_api_key) +export QA_CONSOLE_URL=$(vault_get security-solution-quality-gate qa_console_url) +# The vault entries relevant to the Proxy service (Cloud Handler) +export PROXY_URL=$(vault_get security-solution-quality-gate-proxy proxy_url_test) +export PROXY_CLIENT_ID=$(vault_get security-solution-quality-gate-proxy client_id) +export PROXY_SECRET=$(vault_get security-solution-quality-gate-proxy secret) diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh index cf0dc230b0f404..6507f017504247 100755 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh @@ -16,20 +16,12 @@ export JOB=kibana-security-solution-chrome buildkite-agent meta-data set "${BUILDKITE_JOB_ID}_is_test_execution_step" "true" -mkdir .ftr -vault_get security-quality-gate/role-users data -format=json > .ftr/role_users.json +source .buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh cd x-pack/test/security_solution_cypress set +e -if [ -z "${KIBANA_MKI_USE_LATEST_COMMIT+x}" ] || [ "$KIBANA_MKI_USE_LATEST_COMMIT" = "0" ]; then - KIBANA_OVERRIDE_FLAG=0 -else - KIBANA_OVERRIDE_FLAG=1 -fi - -QA_API_KEY=$(vault_get security-solution-quality-gate qa_api_key) -QA_CONSOLE_URL=$(vault_get security-solution-quality-gate qa_console_url) -BK_ANALYTICS_API_KEY=$(vault_get security-solution-quality-gate $BK_TEST_SUITE_KEY) +export BK_ANALYTICS_API_KEY=$(vault_get security-solution-quality-gate $BK_TEST_SUITE_KEY) -QA_CONSOLE_URL=$QA_CONSOLE_URL KIBANA_MKI_USE_LATEST_COMMIT=$KIBANA_OVERRIDE_FLAG BK_ANALYTICS_API_KEY=$BK_ANALYTICS_API_KEY CLOUD_QA_API_KEY=$QA_API_KEY yarn $1; status=$?; yarn junit:merge || :; exit $status +echo "--- Triggering Kibana tests for $1" +BK_ANALYTICS_API_KEY=$BK_ANALYTICS_API_KEY yarn $1; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh index c1a22d221cafc7..a39c51c07a47c0 100644 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh @@ -2,11 +2,10 @@ echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co KIBANA_BASE_IMAGE="docker.elastic.co/kibana-ci/kibana-serverless" -KIBANA_CURRENT_COMMIT=${KIBANA_BASE_IMAGE}:sec-sol-qg-${BUILDKITE_COMMIT:0:12} KIBANA_LATEST=${KIBANA_BASE_IMAGE}:latest -if [ "$KIBANA_MKI_USE_LATEST_COMMIT" = "1" ]; then - KBN_IMAGE=${KIBANA_CURRENT_COMMIT} +if [ -v KIBANA_MKI_IMAGE_COMMIT ]; then + KBN_IMAGE=${KIBANA_BASE_IMAGE}:git-${KIBANA_MKI_IMAGE_COMMIT:0:12} else KBN_IMAGE=${KIBANA_LATEST} fi diff --git a/.buildkite/scripts/steps/cloud/purge_projects.ts b/.buildkite/scripts/steps/cloud/purge_projects.ts index a8a83266a826e0..84083417f62678 100644 --- a/.buildkite/scripts/steps/cloud/purge_projects.ts +++ b/.buildkite/scripts/steps/cloud/purge_projects.ts @@ -88,7 +88,7 @@ async function purgeProjects() { } else if ( !Boolean( pullRequest.labels.filter((label: any) => - /^ci:project-deploy-(elasticearch|security|observability)$/.test(label.name) + /^ci:project-deploy-(elasticsearch|security|observability)$/.test(label.name) ).length ) ) { diff --git a/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx index cd34d64a8429aa..bcf3f2af51956a 100644 --- a/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx +++ b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx @@ -48,11 +48,26 @@ export interface PanelActionDependencies { licensing: LicensingPluginStart; } +type StartServices = [ + Pick< + CoreStart, + // required for modules that render React + | 'analytics' + | 'i18n' + | 'theme' + // used extensively in Reporting share panel action + | 'application' + | 'uiSettings' + >, + PanelActionDependencies, + unknown +]; + interface Params { apiClient: ReportingAPIClient; csvConfig: ClientConfigType['csv']; core: CoreSetup; - startServices$: Observable<[CoreStart, PanelActionDependencies, unknown]>; + startServices$: Observable; usesUiCapabilities: boolean; } diff --git a/packages/kbn-reporting/public/share/index.ts b/packages/kbn-reporting/public/share/index.ts index 7c7b6819afc071..b2587965858d82 100644 --- a/packages/kbn-reporting/public/share/index.ts +++ b/packages/kbn-reporting/public/share/index.ts @@ -12,4 +12,4 @@ export { reportingScreenshotShareProvider } from './share_context_menu/register_ export { reportingCsvShareProvider } from './share_context_menu/register_csv_reporting'; export { reportingCsvShareProvider as reportingCsvShareModalProvider } from './share_context_menu/register_csv_modal_reporting'; export type { ReportingPublicComponents } from './shared/get_shared_components'; -export type { JobParamsProviderOptions } from './share_context_menu'; +export type { JobParamsProviderOptions, StartServices } from './share_context_menu'; diff --git a/packages/kbn-reporting/public/share/share_context_menu/index.ts b/packages/kbn-reporting/public/share/share_context_menu/index.ts index 1259aee0008ad6..c27ec3f38c68c5 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/index.ts +++ b/packages/kbn-reporting/public/share/share_context_menu/index.ts @@ -6,35 +6,42 @@ * Side Public License, v 1. */ -import type { - ApplicationStart, - I18nStart, - IUiSettingsClient, - ThemeServiceSetup, - ToastsSetup, -} from '@kbn/core/public'; +import * as Rx from 'rxjs'; + +import type { ApplicationStart, CoreStart } from '@kbn/core/public'; import { ILicense } from '@kbn/licensing-plugin/public'; import type { LayoutParams } from '@kbn/screenshotting-plugin/common'; + import type { ReportingAPIClient } from '../../reporting_api_client'; +export type StartServices = [ + Pick< + CoreStart, + // required for modules that render React + | 'analytics' + | 'i18n' + | 'theme' + // used extensively in Reporting share context menus and modal + | 'notifications' + >, + unknown, + unknown +]; + export interface ExportModalShareOpts { apiClient: ReportingAPIClient; - uiSettings: IUiSettingsClient; usesUiCapabilities: boolean; license: ILicense; application: ApplicationStart; - theme: ThemeServiceSetup; - i18n: I18nStart; + startServices$: Rx.Observable; } export interface ExportPanelShareOpts { apiClient: ReportingAPIClient; - toasts: ToastsSetup; - uiSettings: IUiSettingsClient; usesUiCapabilities: boolean; license: ILicense; application: ApplicationStart; - theme: ThemeServiceSetup; + startServices$: Rx.Observable; } export interface ReportingSharingData { diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_csv_modal_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_csv_modal_reporting.tsx index 7525c714de7b25..70225a3033773e 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_csv_modal_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_csv_modal_reporting.tsx @@ -7,14 +7,15 @@ */ import { i18n } from '@kbn/i18n'; -import React from 'react'; import { toMountPoint } from '@kbn/react-kibana-mount'; +import React from 'react'; +import { firstValueFrom } from 'rxjs'; import { CSV_JOB_TYPE, CSV_JOB_TYPE_V2 } from '@kbn/reporting-export-types-csv-common'; import type { SearchSourceFields } from '@kbn/data-plugin/common'; -import { ShareContext, ShareMenuItem } from '@kbn/share-plugin/public'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n-react'; +import { ShareContext, ShareMenuItem } from '@kbn/share-plugin/public'; import type { ExportModalShareOpts } from '.'; import { checkLicense } from '../..'; @@ -23,8 +24,7 @@ export const reportingCsvShareProvider = ({ application, license, usesUiCapabilities, - i18n: i18nStart, - theme, + startServices$, }: ExportModalShareOpts) => { const getShareMenuItems = ({ objectType, sharingData, toasts }: ShareContext) => { if ('search' !== objectType) { @@ -86,7 +86,8 @@ export const reportingCsvShareProvider = ({ const decoratedJobParams = apiClient.getDecoratedJobParams(getJobParams()); return apiClient .createReportingJob(reportType, decoratedJobParams) - .then(() => { + .then(() => firstValueFrom(startServices$)) + .then(([startServices]) => { toasts.addSuccess({ title: intl.formatMessage( { @@ -110,7 +111,7 @@ export const reportingCsvShareProvider = ({ ), }} />, - { theme, i18n: i18nStart } + startServices ), 'data-test-subj': 'queueReportSuccess', }); diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx index 58c380a2201e49..5144d32bc48cda 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_csv_reporting.tsx @@ -19,12 +19,10 @@ import { ReportingPanelContent } from './reporting_panel_content_lazy'; export const reportingCsvShareProvider = ({ apiClient, - toasts, - uiSettings, application, license, usesUiCapabilities, - theme, + startServices$, }: ExportPanelShareOpts): ShareMenuProvider => { const getShareMenuItems = ({ objectType, objectId, sharingData, onClose }: ShareContext) => { if ('search' !== objectType) { @@ -104,14 +102,12 @@ export const reportingCsvShareProvider = ({ ), }, diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_modal_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_modal_reporting.tsx index 1761b8df45878b..621ab6fc5a0d34 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_modal_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_modal_reporting.tsx @@ -7,17 +7,18 @@ */ import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { ShareContext, ShareMenuItem, ShareMenuProvider } from '@kbn/share-plugin/public'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n-react'; import { toMountPoint } from '@kbn/react-kibana-mount'; -import { checkLicense } from '../../license_check'; +import { ShareContext, ShareMenuItem, ShareMenuProvider } from '@kbn/share-plugin/public'; +import React from 'react'; +import { firstValueFrom } from 'rxjs'; import { ExportModalShareOpts, ExportPanelShareOpts, JobParamsProviderOptions, ReportingSharingData, } from '.'; +import { checkLicense } from '../../license_check'; import { ScreenCapturePanelContent } from './screen_capture_panel_content_lazy'; const getJobParams = (opts: JobParamsProviderOptions, type: 'pngV2' | 'printablePdfV2') => () => { @@ -45,12 +46,10 @@ const getJobParams = (opts: JobParamsProviderOptions, type: 'pngV2' | 'printable */ export const reportingScreenshotShareProvider = ({ apiClient, - toasts, - uiSettings, license, application, usesUiCapabilities, - theme, + startServices$, }: ExportPanelShareOpts): ShareMenuProvider => { const getShareMenuItems = ({ objectType, @@ -136,15 +135,13 @@ export const reportingScreenshotShareProvider = ({ content: ( ), }, @@ -169,8 +166,7 @@ export const reportingScreenshotShareProvider = ({ content: ( ), }, @@ -200,8 +195,7 @@ export const reportingExportModalProvider = ({ license, application, usesUiCapabilities, - theme, - i18n: i18nStart, + startServices$, }: ExportModalShareOpts): ShareMenuProvider => { const getShareMenuItems = ({ objectType, @@ -294,7 +288,8 @@ export const reportingExportModalProvider = ({ return apiClient .createReportingJob('printablePdfV2', decoratedJobParams) - .then(() => { + .then(() => firstValueFrom(startServices$)) + .then(([startServices]) => { toasts.addSuccess({ title: intl.formatMessage( { @@ -318,7 +313,7 @@ export const reportingExportModalProvider = ({ ), }} />, - { theme, i18n: i18nStart } + startServices ), 'data-test-subj': 'queueReportSuccess', }); @@ -347,7 +342,8 @@ export const reportingExportModalProvider = ({ }); return apiClient .createReportingJob('pngV2', decoratedJobParams) - .then(() => { + .then(() => firstValueFrom(startServices$)) + .then(([startServices]) => { toasts.addSuccess({ title: intl.formatMessage( { @@ -371,7 +367,7 @@ export const reportingExportModalProvider = ({ ), }} />, - { theme, i18n: i18nStart } + startServices ), 'data-test-subj': 'queueReportSuccess', }); @@ -414,7 +410,6 @@ export const reportingExportModalProvider = ({ /> ), layoutOption: objectType === 'dashboard' ? ('print' as const) : undefined, - theme, renderLayoutOptionSwitch: objectType === 'dashboard', renderCopyURLButton: true, absoluteUrl: new URL(relativePathPDF, window.location.href).toString(), diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx index 6446efb787f665..15e671d2afc64b 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_pdf_png_reporting.tsx @@ -57,12 +57,10 @@ const getJobParams = export const reportingScreenshotShareProvider = ({ apiClient, - toasts, - uiSettings, license, application, usesUiCapabilities, - theme, + startServices$, }: ExportPanelShareOpts): ShareMenuProvider => { const getShareMenuItems = ({ objectType, @@ -150,15 +148,13 @@ export const reportingScreenshotShareProvider = ({ content: ( ), }, @@ -185,8 +181,6 @@ export const reportingScreenshotShareProvider = ({ content: ( ), }, diff --git a/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content/reporting_panel_content.test.tsx b/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content/reporting_panel_content.test.tsx index 67a7433fe08780..133ea782c0bdf2 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content/reporting_panel_content.test.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content/reporting_panel_content.test.tsx @@ -6,14 +6,10 @@ * Side Public License, v 1. */ -import { - httpServiceMock, - notificationServiceMock, - themeServiceMock, - uiSettingsServiceMock, -} from '@kbn/core/public/mocks'; +import { coreMock, httpServiceMock, uiSettingsServiceMock } from '@kbn/core/public/mocks'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; +import * as Rx from 'rxjs'; import { ReportingPanelProps as Props, ReportingPanelContent } from '.'; import { ReportingAPIClient } from '../../..'; import { ErrorUnsavedWorkPanel } from './components'; @@ -23,8 +19,6 @@ jest.mock('./constants', () => ({ getMaxUrlLength: jest.fn(() => 9999999), })); -const theme = themeServiceMock.createSetupContract(); - describe('ReportingPanelContent', () => { const props: Partial = { layoutId: 'super_cool_layout_id_X', @@ -34,7 +28,6 @@ describe('ReportingPanelContent', () => { objectType: 'noice_object', title: 'ultimate_title', }; - const toasts = notificationServiceMock.createSetupContract().toasts; const http = httpServiceMock.createSetupContract(); const uiSettings = uiSettingsServiceMock.createSetupContract(); let apiClient: ReportingAPIClient; @@ -50,6 +43,7 @@ describe('ReportingPanelContent', () => { apiClient = new ReportingAPIClient(http, uiSettings, '7.15.0-test'); }); + const { getStartServices } = coreMock.createSetup(); const mountComponent = (newProps: Partial) => mountWithIntl( { layoutId={props.layoutId} getJobParams={() => jobParams} apiClient={apiClient} - toasts={toasts} - uiSettings={uiSettings} - theme={theme} + startServices$={Rx.from(getStartServices())} {...props} {...newProps} /> diff --git a/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content/reporting_panel_content.tsx b/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content/reporting_panel_content.tsx index bafc355470fad2..a544e6a44464ba 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content/reporting_panel_content.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/reporting_panel_content/reporting_panel_content.tsx @@ -7,6 +7,7 @@ */ import React, { Component, ReactElement } from 'react'; +import * as Rx from 'rxjs'; import { CSV_REPORT_TYPE, CSV_REPORT_TYPE_V2 } from '@kbn/reporting-export-types-csv-common'; import { PDF_REPORT_TYPE, PDF_REPORT_TYPE_V2 } from '@kbn/reporting-export-types-pdf-common'; @@ -22,13 +23,14 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; -import { IUiSettingsClient, ThemeServiceSetup, ToastsSetup } from '@kbn/core/public'; + import { i18n } from '@kbn/i18n'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n-react'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import type { BaseParams } from '@kbn/reporting-common/types'; -import { ReportingAPIClient } from '../../../reporting_api_client'; +import type { StartServices } from '../..'; +import type { ReportingAPIClient } from '../../../reporting_api_client'; import { ErrorUnsavedWorkPanel, ErrorUrlTooLongPanel } from './components'; import { getMaxUrlLength } from './constants'; @@ -38,8 +40,6 @@ import { getMaxUrlLength } from './constants'; */ export interface ReportingPanelProps { apiClient: ReportingAPIClient; - toasts: ToastsSetup; - uiSettings: IUiSettingsClient; reportType: string; requiresSavedState: boolean; // Whether the report to be generated requires saved state that is not captured in the URL submitted to the report generator. @@ -51,7 +51,8 @@ export interface ReportingPanelProps { options?: ReactElement | null; isDirty?: boolean; onClose?: () => void; - theme: ThemeServiceSetup; + + startServices$: Rx.Observable; } export type Props = ReportingPanelProps & { intl: InjectedIntl }; @@ -277,68 +278,66 @@ class ReportingPanelContentUi extends Component { this.setState({ absoluteUrl }); }; - private createReportingJob = () => { - const { intl } = this.props; - const decoratedJobParams = this.props.apiClient.getDecoratedJobParams( - this.props.getJobParams() - ); + private createReportingJob = async () => { + const { startServices$, apiClient, intl } = this.props; + const [coreStart] = await Rx.firstValueFrom(startServices$); + const decoratedJobParams = apiClient.getDecoratedJobParams(this.props.getJobParams()); + const { toasts } = coreStart.notifications; this.setState({ isCreatingReportJob: true }); - return this.props.apiClient - .createReportingJob(this.props.reportType, decoratedJobParams) - .then(() => { - this.props.toasts.addSuccess({ - title: intl.formatMessage( - { - id: 'reporting.share.panelContent.successfullyQueuedReportNotificationTitle', - defaultMessage: 'Queued report for {objectType}', - }, - { objectType: this.state.objectType } - ), - text: toMountPoint( - - - - ), - }} - />, - { theme$: this.props.theme.theme$ } - ), - 'data-test-subj': 'queueReportSuccess', - }); - if (this.props.onClose) { - this.props.onClose(); - } - if (this.mounted) { - this.setState({ isCreatingReportJob: false }); - } - }) - .catch((error) => { - // eslint-disable-next-line no-console - console.error(error); - this.props.toasts.addError(error, { - title: intl.formatMessage({ - id: 'reporting.share.panelContent.notification.reportingErrorTitle', - defaultMessage: 'Unable to create report', - }), - toastMessage: intl.formatMessage({ - id: 'reporting.share.panelContent.notification.reportingErrorToastMessage', - defaultMessage: `We couldn't create a report at this time.`, - }), - }); - if (this.mounted) { - this.setState({ isCreatingReportJob: false }); - } + try { + await this.props.apiClient.createReportingJob(this.props.reportType, decoratedJobParams); + toasts.addSuccess({ + title: intl.formatMessage( + { + id: 'reporting.share.panelContent.successfullyQueuedReportNotificationTitle', + defaultMessage: 'Queued report for {objectType}', + }, + { objectType: this.state.objectType } + ), + text: toMountPoint( + + + + ), + }} + />, + coreStart + ), + 'data-test-subj': 'queueReportSuccess', }); + if (this.props.onClose) { + this.props.onClose(); + } + if (this.mounted) { + this.setState({ isCreatingReportJob: false }); + } + } catch (error) { + // eslint-disable-next-line no-console + console.error(error); + toasts.addError(error, { + title: intl.formatMessage({ + id: 'reporting.share.panelContent.notification.reportingErrorTitle', + defaultMessage: 'Unable to create report', + }), + toastMessage: intl.formatMessage({ + id: 'reporting.share.panelContent.notification.reportingErrorToastMessage', + defaultMessage: `We couldn't create a report at this time.`, + }), + }); + if (this.mounted) { + this.setState({ isCreatingReportJob: false }); + } + } }; } diff --git a/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.test.tsx b/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.test.tsx index 42d599da196226..854ac403e2d7c8 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.test.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/screen_capture_panel_content.test.tsx @@ -6,14 +6,16 @@ * Side Public License, v 1. */ -import { coreMock, themeServiceMock } from '@kbn/core/public/mocks'; +import * as Rx from 'rxjs'; +import { coreMock } from '@kbn/core/public/mocks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { mount } from 'enzyme'; import React from 'react'; import { ReportingAPIClient } from '../..'; import { ScreenCapturePanelContent } from './screen_capture_panel_content'; -const { http, uiSettings, ...coreSetup } = coreMock.createSetup(); +const { http, uiSettings, getStartServices } = coreMock.createSetup(); +const startServices$ = Rx.from(getStartServices()); uiSettings.get.mockImplementation((key: string) => { switch (key) { case 'dateFormat:tz': @@ -28,8 +30,6 @@ const getJobParamsDefault = () => ({ browserTimezone: 'America/New_York', }); -const theme = themeServiceMock.createSetupContract(); - test('ScreenCapturePanelContent renders the default view properly', () => { const component = mount( @@ -37,10 +37,8 @@ test('ScreenCapturePanelContent renders the default view properly', () => { reportType="Analytical App" requiresSavedState={false} apiClient={apiClient} - uiSettings={uiSettings} - toasts={coreSetup.notifications.toasts} getJobParams={getJobParamsDefault} - theme={theme} + startServices$={startServices$} /> ); @@ -57,10 +55,8 @@ test('ScreenCapturePanelContent properly renders a view with "canvas" layout opt reportType="Analytical App" requiresSavedState={false} apiClient={apiClient} - uiSettings={uiSettings} - toasts={coreSetup.notifications.toasts} getJobParams={getJobParamsDefault} - theme={theme} + startServices$={startServices$} /> ); @@ -76,11 +72,9 @@ test('ScreenCapturePanelContent allows POST URL to be copied when objectId is pr reportType="Analytical App" requiresSavedState={false} apiClient={apiClient} - uiSettings={uiSettings} - toasts={coreSetup.notifications.toasts} getJobParams={getJobParamsDefault} objectId={'1234-5'} - theme={theme} + startServices$={startServices$} /> ); @@ -96,10 +90,8 @@ test('ScreenCapturePanelContent does not allow POST URL to be copied when object reportType="Analytical App" requiresSavedState={false} apiClient={apiClient} - uiSettings={uiSettings} - toasts={coreSetup.notifications.toasts} getJobParams={getJobParamsDefault} - theme={theme} + startServices$={startServices$} /> ); @@ -115,10 +107,8 @@ test('ScreenCapturePanelContent properly renders a view with "print" layout opti reportType="Analytical App" requiresSavedState={false} apiClient={apiClient} - uiSettings={uiSettings} - toasts={coreSetup.notifications.toasts} getJobParams={getJobParamsDefault} - theme={theme} + startServices$={startServices$} /> ); @@ -135,10 +125,8 @@ test('ScreenCapturePanelContent decorated job params are visible in the POST URL requiresSavedState={false} isDirty={false} apiClient={apiClient} - uiSettings={uiSettings} - toasts={coreSetup.notifications.toasts} getJobParams={getJobParamsDefault} - theme={theme} + startServices$={startServices$} /> ); diff --git a/packages/kbn-reporting/public/share/shared/get_shared_components.tsx b/packages/kbn-reporting/public/share/shared/get_shared_components.tsx index e9a4499071d97d..bc4ecc24281324 100644 --- a/packages/kbn-reporting/public/share/shared/get_shared_components.tsx +++ b/packages/kbn-reporting/public/share/shared/get_shared_components.tsx @@ -6,13 +6,17 @@ * Side Public License, v 1. */ -import { CoreSetup } from '@kbn/core/public'; -import { PDF_REPORT_TYPE_V2 } from '@kbn/reporting-export-types-pdf-common'; -import { PNG_REPORT_TYPE_V2 } from '@kbn/reporting-export-types-png-common'; import React from 'react'; +import { Observable } from 'rxjs'; + +import { PDF_REPORT_TYPE, PDF_REPORT_TYPE_V2 } from '@kbn/reporting-export-types-pdf-common'; +import { PNG_REPORT_TYPE, PNG_REPORT_TYPE_V2 } from '@kbn/reporting-export-types-png-common'; + +import { StartServices } from '..'; import { ReportingAPIClient } from '../..'; import { ReportingPanelProps } from '../share_context_menu/reporting_panel_content'; import { ScreenCapturePanelContent } from '../share_context_menu/screen_capture_panel_content_lazy'; + /** * Properties for displaying a share menu with Reporting features. */ @@ -53,24 +57,20 @@ export interface ReportingPublicComponents { * Related Discuss issue: https://github.com/elastic/kibana/issues/101422 */ export function getSharedComponents( - core: CoreSetup, - apiClient: ReportingAPIClient + apiClient: ReportingAPIClient, + startServices$: Observable ): ReportingPublicComponents { return { ReportingPanelPDFV2(props: ApplicationProps) { - const getJobParams = props.getJobParams as ReportingPanelProps['getJobParams']; if (props.layoutOption === 'canvas') { return ( ); } else { @@ -78,55 +78,43 @@ export function getSharedComponents( } }, ReportingPanelPNGV2(props: ApplicationProps) { - const getJobParams = props.getJobParams as ReportingPanelProps['getJobParams']; if (props.layoutOption === 'canvas') { return ( ); } }, ReportingModalPDF(props: ApplicationProps) { - const getJobParams = props.getJobParams as ReportingPanelProps['getJobParams']; if (props.layoutOption === 'canvas') { return ( ); } }, ReportingModalPNG(props: ApplicationProps) { - const getJobParams = props.getJobParams as ReportingPanelProps['getJobParams']; if (props.layoutOption === 'canvas') { return ( ); } diff --git a/packages/kbn-reporting/public/tsconfig.json b/packages/kbn-reporting/public/tsconfig.json index 7b36e7eeeb6163..1f17ff412c2869 100644 --- a/packages/kbn-reporting/public/tsconfig.json +++ b/packages/kbn-reporting/public/tsconfig.json @@ -31,5 +31,8 @@ "@kbn/i18n-react", "@kbn/test-jest-helpers", "@kbn/react-kibana-mount", + "@kbn/home-plugin", + "@kbn/management-plugin", + "@kbn/ui-actions-plugin", ] } diff --git a/packages/kbn-reporting/public/types.ts b/packages/kbn-reporting/public/types.ts index 67f5755e367cc1..82da5e5cfa0013 100644 --- a/packages/kbn-reporting/public/types.ts +++ b/packages/kbn-reporting/public/types.ts @@ -6,6 +6,22 @@ * Side Public License, v 1. */ +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { HomePublicPluginStart } from '@kbn/home-plugin/public'; +import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; +import type { ManagementStart } from '@kbn/management-plugin/public'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; + +export interface ReportingPublicPluginStartDependencies { + home: HomePublicPluginStart; + data: DataPublicPluginStart; + management: ManagementStart; + licensing: LicensingPluginStart; + uiActions: UiActionsStart; + share: SharePluginStart; +} + export interface ClientConfigType { csv: { enablePanelActionDownload: boolean; diff --git a/packages/kbn-test/src/auth/session_manager.ts b/packages/kbn-test/src/auth/session_manager.ts index 7e7b862aa84b2f..a8edca981c1d4d 100644 --- a/packages/kbn-test/src/auth/session_manager.ts +++ b/packages/kbn-test/src/auth/session_manager.ts @@ -35,18 +35,23 @@ export interface SamlSessionManagerOptions { * Manages cookies associated with user roles */ export class SamlSessionManager { + private readonly DEFAULT_ROLES_FILE_NAME: string = 'role_users.json'; private readonly isCloud: boolean; private readonly kbnHost: string; private readonly kbnClient: KbnClient; private readonly log: ToolingLog; private readonly roleToUserMap: Map; private readonly sessionCache: Map; - private readonly userRoleFilePath = resolve(REPO_ROOT, '.ftr', 'role_users.json'); private readonly supportedRoles: string[]; + private readonly userRoleFilePath: string; - constructor(options: SamlSessionManagerOptions) { + constructor(options: SamlSessionManagerOptions, rolesFilename?: string) { this.isCloud = options.isCloud; this.log = options.log; + // if the rolesFilename is provided, respect it. Otherwise use DEFAULT_ROLES_FILE_NAME. + const rolesFile = rolesFilename ? rolesFilename : this.DEFAULT_ROLES_FILE_NAME; + this.log.info(`Using the file ${rolesFile} for the role users`); + this.userRoleFilePath = resolve(REPO_ROOT, '.ftr', rolesFile); const hostOptionsWithoutAuth = { protocol: options.hostOptions.protocol, hostname: options.hostOptions.hostname, diff --git a/packages/kbn-test/src/mocha/junit_report_generation.js b/packages/kbn-test/src/mocha/junit_report_generation.js index 001fe79a380612..4b35fba4fb1e62 100644 --- a/packages/kbn-test/src/mocha/junit_report_generation.js +++ b/packages/kbn-test/src/mocha/junit_report_generation.js @@ -7,6 +7,7 @@ */ import { REPO_ROOT } from '@kbn/repo-info'; +import { getCodeOwnersForFile, getPathsWithOwnersReversed } from '@kbn/code-owners'; import { dirname, relative } from 'path'; import { writeFileSync, mkdirSync } from 'fs'; import { inspect } from 'util'; @@ -91,6 +92,9 @@ export function setupJUnitReportGeneration(runner, options = {}) { .filter((node) => node.pending || !results.find((result) => result.node === node)) .map((node) => ({ skipped: true, node })); + // cache codeowners for quicker lookup + const reversedCodeowners = getPathsWithOwnersReversed(); + const builder = xmlBuilder.create( 'testsuites', { encoding: 'utf-8' }, @@ -108,17 +112,26 @@ export function setupJUnitReportGeneration(runner, options = {}) { 'metadata-json': JSON.stringify(metadata ?? {}), }); - function addTestcaseEl(node) { - return testsuitesEl.ele('testcase', { + function addTestcaseEl(node, failed) { + const attrs = { name: getFullTitle(node), classname: `${reportName}.${getPath(node).replace(/\./g, 'ยท')}`, time: getDuration(node), 'metadata-json': JSON.stringify(getTestMetadata(node) || {}), - }); + }; + + // adding code owners only for the failed test case + if (failed) { + const testCaseRelativePath = getPath(node); + const owners = getCodeOwnersForFile(testCaseRelativePath, reversedCodeowners); + attrs.owners = owners || ''; // empty string when no codeowners are defined + } + + return testsuitesEl.ele('testcase', attrs); } [...results, ...skippedResults].forEach((result) => { - const el = addTestcaseEl(result.node); + const el = addTestcaseEl(result.node, result.failed); if (result.failed) { el.ele('system-out').dat(escapeCdata(getSnapshotOfRunnableLogs(result.node) || '')); diff --git a/packages/kbn-test/src/mocha/junit_report_generation.test.js b/packages/kbn-test/src/mocha/junit_report_generation.test.js index ac23d91390ed97..b6bc2e951d1df9 100644 --- a/packages/kbn-test/src/mocha/junit_report_generation.test.js +++ b/packages/kbn-test/src/mocha/junit_report_generation.test.js @@ -54,17 +54,14 @@ describe('dev/mocha/junit report generation', () => { const [testsuite] = report.testsuites.testsuite; expect(testsuite.$.time).toMatch(DURATION_REGEX); expect(testsuite.$.timestamp).toMatch(ISO_DATE_SEC_REGEX); - expect(testsuite).toEqual({ - $: { - failures: '2', - name: 'test', - skipped: '1', - tests: '4', - 'metadata-json': '{}', - time: testsuite.$.time, - timestamp: testsuite.$.timestamp, - }, - testcase: testsuite.testcase, + expect(testsuite.$).toEqual({ + failures: '2', + name: 'test', + skipped: '1', + tests: '4', + 'metadata-json': '{}', + time: testsuite.$.time, + timestamp: testsuite.$.timestamp, }); // there are actually only three tests, but since the hook failed @@ -94,6 +91,7 @@ describe('dev/mocha/junit report generation', () => { name: 'SUITE fails', time: testFail.$.time, 'metadata-json': '{}', + owners: '', }, 'system-out': testFail['system-out'], failure: [testFail.failure[0]], @@ -108,6 +106,7 @@ describe('dev/mocha/junit report generation', () => { name: 'SUITE SUB_SUITE "before each" hook: fail hook for "never runs"', time: beforeEachFail.$.time, 'metadata-json': '{}', + owners: '', }, 'system-out': testFail['system-out'], failure: [beforeEachFail.failure[0]], diff --git a/packages/kbn-text-based-editor/src/history_local_storage.ts b/packages/kbn-text-based-editor/src/history_local_storage.ts index dfe3b2e05b17c7..b6bbdd7d5896a8 100644 --- a/packages/kbn-text-based-editor/src/history_local_storage.ts +++ b/packages/kbn-text-based-editor/src/history_local_storage.ts @@ -102,16 +102,17 @@ export const updateCachedQueries = ( ); let allQueries = [...queriesToStore, ...newQueries]; - if (allQueries.length === maxQueriesAllowed + 1) { + if (allQueries.length >= maxQueriesAllowed + 1) { const sortedByDate = allQueries.sort((a, b) => sortDates(b?.startDateMilliseconds, a?.startDateMilliseconds) ); - // delete the last element - const toBeDeletedQuery = sortedByDate[maxQueriesAllowed]; - cachedQueries.delete(toBeDeletedQuery.queryString); - allQueries = allQueries.filter((q) => { - return q.queryString !== toBeDeletedQuery.queryString; + // queries to store in the localstorage + allQueries = sortedByDate.slice(0, maxQueriesAllowed); + // clear and reset the queries in the cache + cachedQueries.clear(); + allQueries.forEach((queryItem) => { + cachedQueries.set(queryItem.queryString, queryItem); }); } localStorage.setItem(QUERY_HISTORY_ITEM_KEY, JSON.stringify(allQueries)); diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_root.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_root.tsx index f8f23b3c19ef30..a259e3debfb040 100644 --- a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_root.tsx +++ b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_root.tsx @@ -10,7 +10,7 @@ import React, { useMemo } from 'react'; import type { Observable } from 'rxjs'; import { map } from 'rxjs'; import { pick } from 'lodash'; -import { EuiThemeProvider as StyledComponentsThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { EuiSpacer } from '@elastic/eui'; import type { DataView } from '@kbn/data-views-plugin/common'; @@ -90,7 +90,7 @@ export const ChangePointDetectionAppState: FC return ( - + @@ -114,7 +114,7 @@ export const ChangePointDetectionAppState: FC - + ); }; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/show_flyout.tsx b/x-pack/plugins/aiops/public/components/log_categorization/show_flyout.tsx index 843f48400b00d3..87a2e9e934cc36 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/show_flyout.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/show_flyout.tsx @@ -34,8 +34,7 @@ export async function showCategorizeFlyout( originatingApp: string, additionalFilter?: CategorizationAdditionalFilter ): Promise { - const { analytics, http, theme, overlays, application, notifications, uiSettings, i18n } = - coreStart; + const { overlays, application, i18n } = coreStart; return new Promise(async (resolve, reject) => { try { @@ -45,15 +44,10 @@ export async function showCategorizeFlyout( }; const appDependencies: AiopsAppDependencies = { - analytics, - notifications, - uiSettings, - http, - theme, - application, - i18n, + ...coreStart, ...plugins, }; + const startServices = pick(coreStart, 'analytics', 'i18n', 'theme'); const datePickerDeps: DatePickerDependencies = { ...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings']), i18n, @@ -82,7 +76,7 @@ export async function showCategorizeFlyout( , - { theme, i18n } + startServices ), { 'data-test-subj': 'aiopsCategorizeFlyout', diff --git a/x-pack/plugins/aiops/public/embeddable/embeddable_change_point_chart.tsx b/x-pack/plugins/aiops/public/embeddable/embeddable_change_point_chart.tsx index 101b9331e124cb..c76eae5c918d04 100644 --- a/x-pack/plugins/aiops/public/embeddable/embeddable_change_point_chart.tsx +++ b/x-pack/plugins/aiops/public/embeddable/embeddable_change_point_chart.tsx @@ -9,8 +9,7 @@ import React, { Suspense } from 'react'; import ReactDOM from 'react-dom'; import type { EmbeddableInput, EmbeddableOutput, IContainer } from '@kbn/embeddable-plugin/public'; import { Embeddable as AbstractEmbeddable } from '@kbn/embeddable-plugin/public'; -import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; -import type { ThemeServiceStart } from '@kbn/core-theme-browser'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/public'; import type { IUiSettingsClient } from '@kbn/core/public'; @@ -34,7 +33,8 @@ export type EmbeddableChangePointChartInput = EmbeddableInput & EmbeddableChange export type EmbeddableChangePointChartOutput = EmbeddableOutput & { indexPatterns?: DataView[] }; export interface EmbeddableChangePointChartDeps { - theme: ThemeServiceStart; + analytics: CoreStart['analytics']; + theme: CoreStart['theme']; data: DataPublicPluginStart; uiSettings: IUiSettingsClient; http: CoreStart['http']; @@ -116,8 +116,7 @@ export class EmbeddableChangePointChart extends AbstractEmbeddable< // test subject selector for functional tests this.node.setAttribute('data-test-subj', 'aiopsEmbeddableChangePointChart'); - const I18nContext = this.deps.i18n.Context; - + const startServices = pick(this.deps, 'analytics', 'i18n', 'theme'); const datePickerDeps = { ...pick(this.deps, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), uiSettingsKeys: UI_SETTINGS, @@ -132,25 +131,23 @@ export class EmbeddableChangePointChart extends AbstractEmbeddable< } as unknown as AiopsAppDependencies; ReactDOM.render( - - - - - - - - - - - , + + + + + + + + + , el ); } diff --git a/x-pack/plugins/aiops/public/embeddable/embeddable_change_point_chart_factory.ts b/x-pack/plugins/aiops/public/embeddable/embeddable_change_point_chart_factory.ts index 48e3198cd80d17..318cc88fed6fd0 100644 --- a/x-pack/plugins/aiops/public/embeddable/embeddable_change_point_chart_factory.ts +++ b/x-pack/plugins/aiops/public/embeddable/embeddable_change_point_chart_factory.ts @@ -68,22 +68,21 @@ export class EmbeddableChangePointChartFactory implements EmbeddableFactoryDefin async create(input: EmbeddableChangePointChartInput, parent?: IContainer) { try { const [ - { i18n: i18nService, theme, http, uiSettings, notifications }, + { http, uiSettings, notifications, ...startServices }, { lens, data, usageCollection, fieldFormats }, ] = await this.getStartServices(); return new EmbeddableChangePointChart( this.type, { - theme, http, - i18n: i18nService, uiSettings, data, notifications, lens, usageCollection, fieldFormats, + ...startServices, }, input, parent diff --git a/x-pack/plugins/aiops/public/embeddable/handle_explicit_input.tsx b/x-pack/plugins/aiops/public/embeddable/handle_explicit_input.tsx index cd64daee4b8492..35a8fb60b685e8 100644 --- a/x-pack/plugins/aiops/public/embeddable/handle_explicit_input.tsx +++ b/x-pack/plugins/aiops/public/embeddable/handle_explicit_input.tsx @@ -46,7 +46,7 @@ export async function resolveEmbeddableChangePointUserInput( }} /> , - { theme: coreStart.theme, i18n: coreStart.i18n } + coreStart ) ); } catch (error) { diff --git a/x-pack/plugins/aiops/tsconfig.json b/x-pack/plugins/aiops/tsconfig.json index a66b49cda17e29..6b11a3c6b91f42 100644 --- a/x-pack/plugins/aiops/tsconfig.json +++ b/x-pack/plugins/aiops/tsconfig.json @@ -54,7 +54,6 @@ "@kbn/utility-types", "@kbn/presentation-util-plugin", "@kbn/embeddable-plugin", - "@kbn/core-theme-browser", "@kbn/core-lifecycle-browser", "@kbn/cases-plugin", "@kbn/react-kibana-mount", @@ -69,6 +68,8 @@ "@kbn/aiops-log-rate-analysis", "@kbn/aiops-log-pattern-analysis", "@kbn/aiops-change-point-detection", + "@kbn/react-kibana-context-theme", + "@kbn/react-kibana-context-render", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_app_state.tsx b/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_app_state.tsx index 154fee5b87606e..64b89251fd50a3 100644 --- a/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_app_state.tsx +++ b/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_app_state.tsx @@ -16,7 +16,8 @@ import { UrlStateProvider } from '@kbn/ml-url-state'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { DatePickerContextProvider } from '@kbn/ml-date-picker'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; -import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { useLocation } from 'react-router-dom'; import { parse } from 'query-string'; @@ -91,6 +92,7 @@ export const DataDriftDetectionAppState: FC = ( charts, unifiedSearch, }; + const startServices = pick(coreStart, 'analytics', 'i18n', 'theme'); const datePickerDeps = { ...pick(services, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), uiSettingsKeys: UI_SETTINGS, @@ -128,7 +130,7 @@ export const DataDriftDetectionAppState: FC = ( }); return ( - + @@ -148,6 +150,6 @@ export const DataDriftDetectionAppState: FC = ( - + ); }; diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx index 03406ddb8045b4..3b97aa6474e74b 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx @@ -7,7 +7,8 @@ import '../_index.scss'; import type { FC } from 'react'; import React from 'react'; -import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import type { ResultLinks } from '../../../common/app'; import { getCoreStart, getPluginsStart } from '../../kibana_services'; @@ -42,7 +43,7 @@ export const FileDataVisualizer: FC = ({ getAdditionalLinks, resultLinks const CloudContext = cloud?.CloudContextProvider || EmptyContext; return ( - + = ({ getAdditionalLinks, resultLinks /> - + ); }; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx index deeffbb934527b..1b0fcb2d688d21 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx @@ -18,7 +18,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import type { IContainer } from '@kbn/embeddable-plugin/public'; import { Embeddable } from '@kbn/embeddable-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; -import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { DatePickerContextProvider } from '@kbn/ml-date-picker'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import type { DataVisualizerStartDependencies } from '../../../../plugin'; @@ -107,31 +108,28 @@ export class DataVisualizerGridEmbeddable extends Embeddable< super.render(node); this.node = node; - const I18nContext = this.services[0].i18n.Context; - const services = { ...this.services[0], ...this.services[1] }; + const startServices = pick(this.services[0], 'analytics', 'i18n', 'theme'); const datePickerDeps = { ...pick(services, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), uiSettingsKeys: UI_SETTINGS, }; ReactDOM.render( - - - - - }> - this.updateOutput(output)} - /> - - - - - , + + + + }> + this.updateOutput(output)} + /> + + + + , node ); } diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx index 6a8e043bae37a9..7482e686db33ef 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx @@ -14,7 +14,9 @@ import { isEqual } from 'lodash'; import { encode } from '@kbn/rison'; import { i18n } from '@kbn/i18n'; import { Storage } from '@kbn/kibana-utils-plugin/public'; -import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { StorageContextProvider } from '@kbn/ml-local-storage'; import type { DataView } from '@kbn/data-views-plugin/public'; import { getNestedProperty } from '@kbn/ml-nested-property'; @@ -334,6 +336,7 @@ export const IndexDataVisualizer: FC = ({ unifiedSearch, }; + const startServices = pick(coreStart, 'analytics', 'i18n', 'theme'); const datePickerDeps: DatePickerDependencies = { ...pick(services, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), uiSettingsKeys: UI_SETTINGS, @@ -341,28 +344,30 @@ export const IndexDataVisualizer: FC = ({ }; return ( - - - - - {!esql ? ( - - ) : ( - - )} - - - - + + + + + + {!esql ? ( + + ) : ( + + )} + + + + + ); }; diff --git a/x-pack/plugins/data_visualizer/tsconfig.json b/x-pack/plugins/data_visualizer/tsconfig.json index a6a865c86818c4..428311f87b305b 100644 --- a/x-pack/plugins/data_visualizer/tsconfig.json +++ b/x-pack/plugins/data_visualizer/tsconfig.json @@ -78,7 +78,9 @@ "@kbn/visualization-utils", "@kbn/ml-time-buckets", "@kbn/aiops-log-rate-analysis", - "@kbn/discover-utils" + "@kbn/discover-utils", + "@kbn/react-kibana-context-render", + "@kbn/react-kibana-context-theme" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/delete_connector_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/delete_connector_modal.tsx index 84b992c89c86cb..512b0bd384697f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/delete_connector_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/delete_connector_modal.tsx @@ -170,7 +170,7 @@ export const DeleteConnectorModal: React.FC = ({ isCr id="delete-related-index" label={i18n.translate( 'xpack.enterpriseSearch.deleteConnectorModal.euiCheckbox.deleteAlsoRelatedIndexLabel', - { defaultMessage: 'Delete also related index' } + { defaultMessage: 'Also delete related index' } )} checked={shouldDeleteIndex} onChange={() => setShouldDeleteIndex(!shouldDeleteIndex)} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.test.tsx index be855aa1793afd..3d94a6e3b22e26 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.test.tsx @@ -9,6 +9,8 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { LogStream } from '@kbn/logs-shared-plugin/public'; + import { EntSearchLogStream } from '.'; const fakeSourceId = 'fake-source-id'; @@ -17,15 +19,12 @@ describe('EntSearchLogStream', () => { const mockDateNow = jest.spyOn(global.Date, 'now').mockReturnValue(160000000); describe('renders with default props', () => { - /** As a result of the theme provider being added, we have to extract the child component to correctly assert */ const wrapper = shallow( - shallow( - - ).prop('children') + ); it('renders a LogStream (wrapped in React.Suspense) component', () => { - expect(wrapper.type()).toEqual(React.Suspense); + expect(wrapper.type()).toEqual(LogStream); }); it('renders with the empty sourceId', () => { @@ -41,13 +40,11 @@ describe('EntSearchLogStream', () => { describe('renders custom props', () => { it('overrides the default props', () => { const wrapper = shallow( - shallow( - - ).prop('children') + ); expect(wrapper.prop('logView')).toEqual({ type: 'log-view-reference', logViewId: 'test' }); @@ -57,12 +54,10 @@ describe('EntSearchLogStream', () => { it('allows passing a custom hoursAgo that modifies the default start timestamp', () => { const wrapper = shallow( - shallow( - - ).prop('children') + ); expect(wrapper.prop('startTimestamp')).toEqual(156400000); @@ -71,18 +66,16 @@ describe('EntSearchLogStream', () => { it('allows passing any prop that the LogStream component takes', () => { const wrapper = shallow( - shallow( - - ).prop('children') + ); expect(wrapper.prop('height')).toEqual(500); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx index 79884fd02848f3..5748d306b6319f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/log_stream/log_stream.tsx @@ -7,7 +7,6 @@ import React from 'react'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { LogStream, LogStreamProps } from '@kbn/logs-shared-plugin/public'; /* @@ -35,9 +34,5 @@ export const EntSearchLogStream: React.FC = ({ if (!endTimestamp) endTimestamp = Date.now(); if (!startTimestamp) startTimestamp = endTimestamp - hoursAgo * 60 * 60 * 1000; - return ( - - - - ); + return ; }; diff --git a/x-pack/plugins/enterprise_search/tsconfig.json b/x-pack/plugins/enterprise_search/tsconfig.json index 2750299a8f6a09..4aba94dff1bbcd 100644 --- a/x-pack/plugins/enterprise_search/tsconfig.json +++ b/x-pack/plugins/enterprise_search/tsconfig.json @@ -64,7 +64,6 @@ "@kbn/core-http-browser-mocks", "@kbn/core-application-browser", "@kbn/core-capabilities-common", - "@kbn/react-kibana-context-theme", "@kbn/code-editor", "@kbn/console-plugin", "@kbn/core-notifications-browser", @@ -73,6 +72,7 @@ "@kbn/search-playground", "@kbn/utility-types", "@kbn/index-management", - "@kbn/deeplinks-search" + "@kbn/deeplinks-search", + "@kbn/react-kibana-context-theme" ] } diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index 05e18a66a72fec..7a965a1541855c 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -14,7 +14,8 @@ import type { AppMountParameters, CoreStart } from '@kbn/core/public'; import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; -import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { StorageContextProvider } from '@kbn/ml-local-storage'; import useLifecycles from 'react-use/lib/useLifecycles'; import useObservable from 'react-use/lib/useObservable'; @@ -118,37 +119,35 @@ const App: FC = ({ if (!licenseReady || !mlCapabilities) return null; + const startServices = pick(coreStart, 'analytics', 'i18n', 'theme'); const datePickerDeps: DatePickerDependencies = { ...pick(services, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), uiSettingsKeys: UI_SETTINGS, showFrozenDataTierChoice: !isServerless, }; - const I18nContext = coreStart.i18n.Context; const ApplicationUsageTrackingProvider = deps.usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment; return ( - - - - - - - - - - - - - - - + + + + + + + + + + + + + ); }; diff --git a/x-pack/plugins/ml/public/application/components/header_menu_portal/header_menu_portal.tsx b/x-pack/plugins/ml/public/application/components/header_menu_portal/header_menu_portal.tsx index 5903b0a81eac78..f80c8888ee1814 100644 --- a/x-pack/plugins/ml/public/application/components/header_menu_portal/header_menu_portal.tsx +++ b/x-pack/plugins/ml/public/application/components/header_menu_portal/header_menu_portal.tsx @@ -19,7 +19,6 @@ export interface HeaderMenuPortalProps { export const HeaderMenuPortal: FC = ({ children }) => { const { services } = useMlKibana(); - const { theme, i18n } = services; const { setHeaderActionMenu } = useContext(MlPageControlsContext); @@ -35,7 +34,7 @@ export const HeaderMenuPortal: FC = ({ children }) => { , - { theme, i18n } + services ); return mount(element); }); @@ -44,8 +43,7 @@ export const HeaderMenuPortal: FC = ({ children }) => { portalNode.unmount(); setHeaderActionMenu(undefined); }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [portalNode, setHeaderActionMenu, services.theme.theme$]); + }, [portalNode, setHeaderActionMenu, services]); return {children}; }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx index 54ca887fcdce33..55f25f70253a74 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx @@ -414,8 +414,7 @@ export const useNavigateToWizardWithClonedJob = () => { data: { dataViews }, http: { basePath }, application: { capabilities }, - theme, - i18n: i18nStart, + ...startServices }, } = useMlKibana(); const navigateToPath = useNavigateToPath(); @@ -464,7 +463,7 @@ export const useNavigateToWizardWithClonedJob = () => { ) : null} , - { theme, i18n: i18nStart } + startServices ), }); } diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx index 4e12c16356401b..7f5929354b61f9 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx +++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx @@ -23,7 +23,8 @@ import { import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { ManagementAppMountParams } from '@kbn/management-plugin/public'; -import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { SpacesContextProps, SpacesPluginStart } from '@kbn/spaces-plugin/public'; @@ -73,8 +74,6 @@ export const JobsListPage: FC = ({ const [isPlatinumOrTrialLicense, setIsPlatinumOrTrialLicense] = useState(true); const [showSyncFlyout, setShowSyncFlyout] = useState(false); const [currentTabId, setCurrentTabId] = useState('anomaly-detector'); - const I18nContext = coreStart.i18n.Context; - const theme$ = coreStart.theme.theme$; const mlServices = useMemo( () => getMlGlobalServices(coreStart.http, data.dataViews, usageCollection), @@ -122,87 +121,85 @@ export const JobsListPage: FC = ({ } return ( - - - - - - - - + + + + + + + } + description={ + + } + rightSideItems={[]} + bottomBorder + paddingSize={'none'} + /> + + + + + + + <> + setShowSyncFlyout(true)} + data-test-subj="mlStackMgmtSyncButton" + > + {i18n.translate('xpack.ml.management.jobsList.syncFlyoutButton', { + defaultMessage: 'Synchronize saved objects', + })} + + {showSyncFlyout && } + + + + + - } - description={ - - } - rightSideItems={[]} - bottomBorder - paddingSize={'none'} - /> - - - - - - - <> - setShowSyncFlyout(true)} - data-test-subj="mlStackMgmtSyncButton" - > - {i18n.translate('xpack.ml.management.jobsList.syncFlyoutButton', { - defaultMessage: 'Synchronize saved objects', - })} - - {showSyncFlyout && } - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + ); }; diff --git a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx index 368cb73e66071a..cef8c87cdea293 100644 --- a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx +++ b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx @@ -28,7 +28,7 @@ import { EuiSelect, EuiSpacer, } from '@elastic/eui'; -import type { I18nStart, OverlayStart, ThemeServiceStart } from '@kbn/core/public'; +import type { CoreStart, OverlayStart } from '@kbn/core/public'; import { css } from '@emotion/react'; import { numberValidator } from '@kbn/ml-agg-utils'; import { toMountPoint } from '@kbn/react-kibana-mount'; @@ -529,8 +529,7 @@ export const StartUpdateDeploymentModal: FC = ({ export const getUserInputModelDeploymentParamsProvider = ( overlays: OverlayStart, - theme: ThemeServiceStart, - i18nStart: I18nStart, + startServices: Pick, startModelDeploymentDocUrl: string ) => ( @@ -563,7 +562,7 @@ export const getUserInputModelDeploymentParamsProvider = resolve(); }} />, - { theme, i18n: i18nStart } + startServices ) ); } catch (e) { diff --git a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx index 9f50c2881d6d64..171143a581194d 100644 --- a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx +++ b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx @@ -10,7 +10,7 @@ import type { EuiCheckboxGroupOption } from '@elastic/eui'; import { EuiCallOut, EuiCheckboxGroup, EuiConfirmModal, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; -import type { I18nStart, OverlayStart, ThemeServiceStart } from '@kbn/core/public'; +import type { CoreStart, OverlayStart } from '@kbn/core/public'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { isDefined } from '@kbn/ml-is-defined'; import { toMountPoint } from '@kbn/react-kibana-mount'; @@ -219,7 +219,7 @@ export const StopModelDeploymentsConfirmDialog: FC + (overlays: OverlayStart, startServices: Pick) => async (forceStopModel: ModelItem): Promise => { return new Promise(async (resolve, reject) => { try { @@ -236,7 +236,7 @@ export const getUserConfirmationProvider = resolve(deploymentIds); }} />, - { theme, i18n: i18nStart } + startServices ) ); } catch (e) { diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx index 5d690a7a58fd89..b9f17084bd81ff 100644 --- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx @@ -58,10 +58,9 @@ export function useModelActions({ services: { application: { navigateToUrl }, overlays, - theme, - i18n: i18nStart, docLinks, mlServices: { mlApiServices }, + ...startServices }, } = useMlKibana(); @@ -109,19 +108,18 @@ export function useModelActions({ }, [mlApiServices]); const getUserConfirmation = useMemo( - () => getUserConfirmationProvider(overlays, theme, i18nStart), - [i18nStart, overlays, theme] + () => getUserConfirmationProvider(overlays, startServices), + [overlays, startServices] ); const getUserInputModelDeploymentParams = useMemo( () => getUserInputModelDeploymentParamsProvider( overlays, - theme, - i18nStart, + startServices, startModelDeploymentDocUrl ), - [overlays, theme, i18nStart, startModelDeploymentDocUrl] + [overlays, startServices, startModelDeploymentDocUrl] ); const isBuiltInModel = useCallback( diff --git a/x-pack/plugins/ml/public/application/routing/routes/explorer/state_manager.tsx b/x-pack/plugins/ml/public/application/routing/routes/explorer/state_manager.tsx index bcb5c200cdf074..1c32c298047113 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/explorer/state_manager.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/explorer/state_manager.tsx @@ -12,7 +12,6 @@ import useObservable from 'react-use/lib/useObservable'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { EuiThemeProvider as StyledComponentsThemeProvider } from '@kbn/kibana-react-plugin/common'; import { useUrlState } from '@kbn/ml-url-state'; import { useTimefilter } from '@kbn/ml-date-picker'; import { ML_JOB_ID } from '@kbn/ml-anomaly-utils'; @@ -217,31 +216,29 @@ export const ExplorerUrlStateManager: FC = ({ - - - - {jobsWithTimeRange.length === 0 ? ( - - ) : ( - - )} - - - + + + {jobsWithTimeRange.length === 0 ? ( + + ) : ( + + )} + + ); }; diff --git a/x-pack/plugins/ml/public/application/routing/use_active_route.tsx b/x-pack/plugins/ml/public/application/routing/use_active_route.tsx index 3974b13eb51de7..202c552559c1bb 100644 --- a/x-pack/plugins/ml/public/application/routing/use_active_route.tsx +++ b/x-pack/plugins/ml/public/application/routing/use_active_route.tsx @@ -24,7 +24,7 @@ export const useActiveRoute = (routesList: MlRoute[]): MlRoute => { const { pathname } = useLocation(); const { - services: { executionContext, overlays, theme, i18n }, + services: { executionContext, overlays, ...startServices }, } = useMlKibana(); /** @@ -78,7 +78,7 @@ export const useActiveRoute = (routesList: MlRoute[]): MlRoute => { />

, - { theme, i18n } + startServices ) ); @@ -90,7 +90,7 @@ export const useActiveRoute = (routesList: MlRoute[]): MlRoute => { }, 15000); } }, - [activeRoute, overlays, theme, pathname, i18n] + [activeRoute, overlays, pathname, startServices] ); useExecutionContext(executionContext, { diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable.tsx index ded0f536ae96ae..9d7b08fa935d09 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable.tsx @@ -10,7 +10,8 @@ import ReactDOM from 'react-dom'; import type { CoreStart } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { Subject, Subscription, type BehaviorSubject } from 'rxjs'; -import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import type { IContainer } from '@kbn/embeddable-plugin/public'; import { embeddableInputToSubject } from '@kbn/embeddable-plugin/public'; import { embeddableOutputToSubject } from '@kbn/embeddable-plugin/public'; @@ -91,38 +92,33 @@ export class AnomalyChartsEmbeddable extends AnomalyDetectionEmbeddable< // required for the export feature to work this.node.setAttribute('data-shared-item', ''); - const I18nContext = this.services[0].i18n.Context; - const theme$ = this.services[0].theme.theme$; - ReactDOM.render( - - - - }> - - - - - , + + + }> + + + + , node ); } diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx index 2dd09620dbbd18..0244f949e467af 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx @@ -23,7 +23,7 @@ export async function resolveEmbeddableAnomalyChartsUserInput( dataViews: DataViewsContract, input?: AnomalyChartsEmbeddableInput ): Promise> { - const { http, overlays, theme, i18n } = coreStart; + const { http, overlays, ...startServices } = coreStart; const { getJobs } = mlApiServicesProvider(new HttpService(http)); @@ -52,7 +52,7 @@ export async function resolveEmbeddableAnomalyChartsUserInput( reject(); }} />, - { theme, i18n } + startServices ) ); } catch (error) { diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.tsx index 665e0b99ddd72b..c333869dd82955 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.tsx @@ -21,7 +21,7 @@ import { initializeTitles, useBatchedPublishingSubjects, } from '@kbn/presentation-publishing'; -import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import React, { useCallback, useState } from 'react'; import useUnmount from 'react-use/lib/useUnmount'; import type { Observable } from 'rxjs'; @@ -210,10 +210,9 @@ export const getAnomalySwimLaneEmbeddableFactory = ( return { api, Component: () => { - const { theme, i18n, uiSettings } = coreStartServices; + const { uiSettings } = coreStartServices; const { uiActions } = pluginsStartServices; - const I18nContext = i18n.Context; const timeBuckets = useTimeBuckets(uiSettings); if (!apiHasExecutionContext(parentApi)) { @@ -279,61 +278,59 @@ export const getAnomalySwimLaneEmbeddableFactory = ( } return ( - - - -
- + +
+ chartWidth$.next(size)} + selection={selectedCells} + onCellsSelection={onCellsSelection} + onPaginationChange={(update) => { + if (update.fromPage) { + api.updatePagination({ fromPage: update.fromPage }); } - onResize={(size) => chartWidth$.next(size)} - selection={selectedCells} - onCellsSelection={onCellsSelection} - onPaginationChange={(update) => { - if (update.fromPage) { - api.updatePagination({ fromPage: update.fromPage }); - } - if (update.perPage) { - api.updatePagination({ perPage: update.perPage, fromPage: 1 }); - } - }} - isLoading={dataLoading.value!} - yAxisWidth={{ max: Y_AXIS_LABEL_WIDTH }} - noDataWarning={ - - - - } - /> + if (update.perPage) { + api.updatePagination({ perPage: update.perPage, fromPage: 1 }); } - chartsService={pluginsStartServices.charts} - onRenderComplete={onRenderComplete} - /> -
-
- - + }} + isLoading={dataLoading.value!} + yAxisWidth={{ max: Y_AXIS_LABEL_WIDTH }} + noDataWarning={ + + + + } + /> + } + chartsService={pluginsStartServices.charts} + onRenderComplete={onRenderComplete} + /> +
+
+ ); }, }; diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx index ed795ab0f9ce52..596da35f66d4cc 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx @@ -23,7 +23,7 @@ export async function resolveAnomalySwimlaneUserInput( dataViews: DataViewsContract, input?: Partial ): Promise { - const { http, overlays, theme, i18n } = coreStart; + const { http, overlays, ...startServices } = coreStart; const { getJobs } = mlApiServicesProvider(new HttpService(http)); @@ -52,7 +52,7 @@ export async function resolveAnomalySwimlaneUserInput( reject(); }} />, - { theme, i18n } + startServices ) ); } catch (error) { diff --git a/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx b/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx index 9d6c394a8e4e2d..99b301ee19e9f1 100644 --- a/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx +++ b/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx @@ -34,9 +34,8 @@ export async function resolveJobSelection( const { http, uiSettings, - theme, - i18n, application: { currentAppId$ }, + ...startServices } = coreStart; return new Promise(async (resolve, reject) => { @@ -86,7 +85,7 @@ export async function resolveJobSelection( maps={maps} /> , - { theme, i18n } + startServices ), { 'data-test-subj': 'mlFlyoutJobSelector', diff --git a/x-pack/plugins/ml/public/embeddables/job_creation/common/create_flyout.tsx b/x-pack/plugins/ml/public/embeddables/job_creation/common/create_flyout.tsx index 461f67a47aa2ee..5bab4967f0cdb1 100644 --- a/x-pack/plugins/ml/public/embeddables/job_creation/common/create_flyout.tsx +++ b/x-pack/plugins/ml/public/embeddables/job_creation/common/create_flyout.tsx @@ -31,10 +31,9 @@ export function createFlyout( ): Promise { const { http, - theme, - i18n, overlays, application: { currentAppId$ }, + ...startServices } = coreStart; return new Promise(async (resolve, reject) => { @@ -63,7 +62,7 @@ export function createFlyout( }} /> , - { theme, i18n } + startServices ), { 'data-test-subj': 'mlFlyoutLayerSelector', diff --git a/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_embeddable.tsx b/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_embeddable.tsx index 8cbd2ad30ae8e1..c0d7902c5ab66b 100644 --- a/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_embeddable.tsx +++ b/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_embeddable.tsx @@ -14,7 +14,8 @@ import { Subject, Subscription, type BehaviorSubject } from 'rxjs'; import type { CoreStart } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; -import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import type { IContainer } from '@kbn/embeddable-plugin/public'; import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; @@ -113,9 +114,7 @@ export class SingleMetricViewerEmbeddable extends Embeddable< // required for the export feature to work this.node.setAttribute('data-shared-item', ''); - const I18nContext = this.services[0].i18n.Context; - const theme$ = this.services[0].theme.theme$; - + const startServices = pick(this.services[0], 'analytics', 'i18n', 'theme'); const datePickerDeps: DatePickerDependencies = { ...pick(this.services[0], ['http', 'notifications', 'theme', 'uiSettings', 'i18n']), data: this.services[1].data, @@ -124,36 +123,34 @@ export class SingleMetricViewerEmbeddable extends Embeddable< }; ReactDOM.render( - - - - - }> - - - - - - , + + + + }> + + + + + , node ); } diff --git a/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_setup_flyout.tsx b/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_setup_flyout.tsx index 11927e9bca377f..92e77f788fc3f4 100644 --- a/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_setup_flyout.tsx +++ b/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_setup_flyout.tsx @@ -22,7 +22,7 @@ export async function resolveEmbeddableSingleMetricViewerUserInput( mlApiServices: MlApiServices, input?: SingleMetricViewerEmbeddableInput ): Promise> { - const { overlays, theme, i18n } = coreStart; + const { overlays, ...startServices } = coreStart; const timefilter = pluginStart.data.query.timefilter.timefilter; return new Promise(async (resolve, reject) => { @@ -62,7 +62,7 @@ export async function resolveEmbeddableSingleMetricViewerUserInput( }} /> , - { theme, i18n } + startServices ) ); } catch (error) { diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index b8cc406eaeadf6..ee36bd73828434 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -133,6 +133,10 @@ export class MlServerPlugin category: DEFAULT_APP_CATEGORIES.kibana, app: [PLUGIN_ID, 'kibana'], catalogue: [PLUGIN_ID, `${PLUGIN_ID}_file_data_visualizer`], + privilegesTooltip: i18n.translate('xpack.ml.featureRegistry.privilegesTooltip', { + defaultMessage: + 'Granting All or Read feature privilege for Machine Learning will also grant the equivalent feature privileges to certain types of Kibana saved objects, namely index patterns, dashboards, saved searches and visualizations as well as machine learning job, trained model and module saved objects.', + }), management: { insightsAndAlerting: ['jobsListLink', 'triggersActions'], }, diff --git a/x-pack/plugins/ml/tsconfig.json b/x-pack/plugins/ml/tsconfig.json index 16ab6806868a57..dba33a6043bc81 100644 --- a/x-pack/plugins/ml/tsconfig.json +++ b/x-pack/plugins/ml/tsconfig.json @@ -121,10 +121,10 @@ "@kbn/ml-time-buckets", "@kbn/aiops-change-point-detection", "@kbn/inference_integration_flyout", - "@kbn/react-kibana-context-theme", "@kbn/presentation-containers", "@kbn/presentation-panel-plugin", "@kbn/shared-ux-utility", "@kbn/discover-utils", + "@kbn/react-kibana-context-render", ], } diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx b/x-pack/plugins/observability_solution/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx index 47c826178bc9b0..af3bbb0ae92732 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx +++ b/x-pack/plugins/observability_solution/infra/public/alerting/log_threshold/components/expression_editor/criterion_preview_chart.tsx @@ -333,7 +333,11 @@ const CriterionPreviewChart: React.FC = ({ tickFormat={yAxisFormatter} domain={chartDomain} /> - + diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/onboarding_flow_form.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/onboarding_flow_form.tsx index 8714917f21a321..197435a396432f 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/onboarding_flow_form.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/onboarding_flow_form.tsx @@ -26,6 +26,7 @@ import { OnboardingFlowPackageList } from '../packages_list'; import { useCustomMargin } from '../shared/use_custom_margin'; import { Category } from './types'; import { useCustomCardsForCategory } from './use_custom_cards_for_category'; +import { useVirtualSearchResults } from './use_virtual_search_results'; interface UseCaseOption { id: Category; @@ -140,6 +141,7 @@ export const OnboardingFlowForm: FunctionComponent = () => { createCollectionCardHandler, searchParams.get('category') as Category | null ); + const virtualSearchResults = useVirtualSearchResults(); return ( @@ -216,10 +218,12 @@ export const OnboardingFlowForm: FunctionComponent = () => { setSearchQuery={setIntegrationSearch} flowCategory={searchParams.get('category')} ref={packageListRef} - customCards={customCards?.filter( - // Filter out collection cards and regular integrations that show up via search anyway - (card) => card.type === 'virtual' && !card.isCollectionCard - )} + customCards={customCards + ?.filter( + // Filter out collection cards and regular integrations that show up via search anyway + (card) => card.type === 'virtual' && !card.isCollectionCard + ) + .concat(virtualSearchResults)} joinCardLists /> diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/use_virtual_search_results.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/use_virtual_search_results.ts new file mode 100644 index 00000000000000..5ee39768d8a583 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/use_virtual_search_results.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { CustomCard } from '../packages_list/types'; + +export function useVirtualSearchResults(): [CustomCard] { + const { + services: { application }, + } = useKibana(); + const getUrlForApp = application?.getUrlForApp; + + return [ + { + id: 'upload-file-virtual', + type: 'virtual', + title: i18n.translate('xpack.observability_onboarding.packageList.uploadFileTitle', { + defaultMessage: 'Upload a file', + }), + description: i18n.translate( + 'xpack.observability_onboarding.packageList.uploadFileDescription', + { + defaultMessage: + 'Upload data from a CSV, TSV, JSON or other log file to Elasticsearch for analysis.', + } + ), + name: 'upload-file', + categories: [], + icons: [ + { + type: 'eui', + src: 'addDataApp', + }, + ], + url: `${getUrlForApp?.('home')}#/tutorial_directory/fileDataViz`, + version: '', + integration: '', + isCollectionCard: false, + }, + ]; +} diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/packages_list/index.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/packages_list/index.tsx index 0e043d113423c9..4a204d0bc56013 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/packages_list/index.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/packages_list/index.tsx @@ -62,7 +62,7 @@ const PackageListGridWrapper = ({ }: WrapperProps) => { const customMargin = useCustomMargin(); const { filteredCards, isLoading } = useAvailablePackages({ - prereleaseIntegrationsEnabled: true, + prereleaseIntegrationsEnabled: false, }); const list: IntegrationCardItem[] = useIntegrationCardList( @@ -97,8 +97,10 @@ const PackageListGridWrapper = ({ box={{ incremental: true, }} - onChange={(arg) => { - setSearchQuery?.(arg.queryText); + onChange={({ queryText, error }) => { + if (error) return; + + setSearchQuery?.(queryText); }} query={searchQuery ?? ''} /> diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts index 37ef7967ae2876..88a624df552800 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { NotificationsStart } from '@kbn/core/public'; -import { coreMock, docLinksServiceMock, themeServiceMock } from '@kbn/core/public/mocks'; +import { coreMock } from '@kbn/core/public/mocks'; import { JobId, ReportApiJSON } from '@kbn/reporting-common/types'; import { JobSummary, JobSummarySet } from '../types'; @@ -43,19 +42,10 @@ jobQueueClientMock.getError = () => Promise.resolve('this is the failed report e jobQueueClientMock.getManagementLink = () => '/#management'; jobQueueClientMock.getReportURL = () => '/reporting/download/job-123'; -const mockShowDanger = jest.fn(); -const mockShowSuccess = jest.fn(); -const mockShowWarning = jest.fn(); -const notificationsMock = { - toasts: { - addDanger: mockShowDanger, - addSuccess: mockShowSuccess, - addWarning: mockShowWarning, - }, -} as unknown as NotificationsStart; - -const theme = themeServiceMock.createStartContract(); -const docLink = docLinksServiceMock.createStartContract(); +const core = coreMock.createStart(); +const mockShowDanger = jest.spyOn(core.notifications.toasts, 'addDanger'); +const mockShowSuccess = jest.spyOn(core.notifications.toasts, 'addSuccess'); +const mockShowWarning = jest.spyOn(core.notifications.toasts, 'addWarning'); describe('stream handler', () => { afterEach(() => { @@ -63,23 +53,13 @@ describe('stream handler', () => { }); it('constructs', () => { - const sh = new TestReportingNotifierStreamHandler( - notificationsMock, - jobQueueClientMock, - theme, - docLink - ); + const sh = new TestReportingNotifierStreamHandler(jobQueueClientMock, core); expect(sh).not.toBe(null); }); describe('findChangedStatusJobs', () => { it('finds no changed status jobs from empty', (done) => { - const sh = new TestReportingNotifierStreamHandler( - notificationsMock, - jobQueueClientMock, - theme, - docLink - ); + const sh = new TestReportingNotifierStreamHandler(jobQueueClientMock, core); const findJobs = sh.testFindChangedStatusJobs([]); findJobs.subscribe((data) => { expect(data).toEqual({ completed: [], failed: [] }); @@ -88,12 +68,7 @@ describe('stream handler', () => { }); it('finds changed status jobs', (done) => { - const sh = new TestReportingNotifierStreamHandler( - notificationsMock, - jobQueueClientMock, - theme, - docLink - ); + const sh = new TestReportingNotifierStreamHandler(jobQueueClientMock, core); const findJobs = sh.testFindChangedStatusJobs([ 'job-source-mock1', 'job-source-mock2', @@ -110,12 +85,7 @@ describe('stream handler', () => { describe('showNotifications', () => { it('show success', (done) => { - const sh = new TestReportingNotifierStreamHandler( - notificationsMock, - jobQueueClientMock, - theme, - docLink - ); + const sh = new TestReportingNotifierStreamHandler(jobQueueClientMock, core); sh.testShowNotifications({ completed: [ { @@ -136,12 +106,7 @@ describe('stream handler', () => { }); it('show max length warning', (done) => { - const sh = new TestReportingNotifierStreamHandler( - notificationsMock, - jobQueueClientMock, - theme, - docLink - ); + const sh = new TestReportingNotifierStreamHandler(jobQueueClientMock, core); sh.testShowNotifications({ completed: [ { @@ -163,12 +128,7 @@ describe('stream handler', () => { }); it('show csv formulas warning', (done) => { - const sh = new TestReportingNotifierStreamHandler( - notificationsMock, - jobQueueClientMock, - theme, - docLink - ); + const sh = new TestReportingNotifierStreamHandler(jobQueueClientMock, core); sh.testShowNotifications({ completed: [ { @@ -190,12 +150,7 @@ describe('stream handler', () => { }); it('show failed job toast', (done) => { - const sh = new TestReportingNotifierStreamHandler( - notificationsMock, - jobQueueClientMock, - theme, - docLink - ); + const sh = new TestReportingNotifierStreamHandler(jobQueueClientMock, core); sh.testShowNotifications({ completed: [], failed: [ @@ -216,12 +171,7 @@ describe('stream handler', () => { }); it('show multiple toast', (done) => { - const sh = new TestReportingNotifierStreamHandler( - notificationsMock, - jobQueueClientMock, - theme, - docLink - ); + const sh = new TestReportingNotifierStreamHandler(jobQueueClientMock, core); sh.testShowNotifications({ completed: [ { diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.ts b/x-pack/plugins/reporting/public/lib/stream_handler.ts index 049aea96e1af2f..78513de46c8018 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.ts @@ -8,7 +8,7 @@ import * as Rx from 'rxjs'; import { catchError, filter, map, mergeMap, takeUntil } from 'rxjs'; -import { DocLinksStart, NotificationsSetup, ThemeServiceStart } from '@kbn/core/public'; +import { CoreStart } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { JOB_STATUS } from '@kbn/reporting-common'; import { JobId } from '@kbn/reporting-common/types'; @@ -42,18 +42,14 @@ function getReportStatus(src: Job): JobSummary { }; } -function handleError( - err: Error, - notifications: NotificationsSetup, - theme: ThemeServiceStart -): Rx.Observable { - notifications.toasts.addDanger( +function handleError(core: CoreStart, err: Error): Rx.Observable { + core.notifications.toasts.addDanger( getGeneralErrorToast( i18n.translate('xpack.reporting.publicNotifier.pollingErrorMessage', { defaultMessage: 'Reporting notifier error!', }), err, - theme + core ) ); window.console.error(err); @@ -63,12 +59,7 @@ function handleError( export class ReportingNotifierStreamHandler { private jobCompletionNotifications = jobCompletionNotifications(); - constructor( - private notifications: NotificationsSetup, - private apiClient: ReportingAPIClient, - private theme: ThemeServiceStart, - private docLinks: DocLinksStart - ) {} + constructor(private apiClient: ReportingAPIClient, private core: CoreStart) {} public startPolling(interval: number, stop$: Rx.Observable) { Rx.timer(0, interval) @@ -81,7 +72,7 @@ export class ReportingNotifierStreamHandler { catchError((err) => { // eslint-disable-next-line no-console console.error(err); - return handleError(err, this.notifications, this.theme); + return handleError(this.core, err); }) ) .subscribe(); @@ -94,10 +85,10 @@ export class ReportingNotifierStreamHandler { completed: completedJobs, failed: failedJobs, }: JobSummarySet): Rx.Observable { - const notifications = this.notifications; + const notifications = this.core.notifications; const apiClient = this.apiClient; - const theme = this.theme; - const docLinks = this.docLinks; + const core = this.core; + const docLinks = this.core.docLinks; const getManagementLink = apiClient.getManagementLink.bind(apiClient); const getDownloadLink = apiClient.getDownloadLink.bind(apiClient); @@ -108,22 +99,22 @@ export class ReportingNotifierStreamHandler { for (const job of completedJobs ?? []) { if (job.csvContainsFormulas) { notifications.toasts.addWarning( - getWarningFormulasToast(job, getManagementLink, getDownloadLink, theme), + getWarningFormulasToast(job, getManagementLink, getDownloadLink, core), completedOptions ); } else if (job.maxSizeReached) { notifications.toasts.addWarning( - getWarningMaxSizeToast(job, getManagementLink, getDownloadLink, theme), + getWarningMaxSizeToast(job, getManagementLink, getDownloadLink, core), completedOptions ); } else if (job.status === JOB_STATUS.WARNINGS) { notifications.toasts.addWarning( - getWarningToast(job, getManagementLink, getDownloadLink, theme), + getWarningToast(job, getManagementLink, getDownloadLink, core), completedOptions ); } else { notifications.toasts.addSuccess( - getSuccessToast(job, getManagementLink, getDownloadLink, theme), + getSuccessToast(job, getManagementLink, getDownloadLink, core), completedOptions ); } @@ -132,8 +123,8 @@ export class ReportingNotifierStreamHandler { // no download link available for (const job of failedJobs ?? []) { const errorText = await apiClient.getError(job.id); - this.notifications.toasts.addDanger( - getFailureToast(errorText, job, getManagementLink, theme, docLinks) + notifications.toasts.addDanger( + getFailureToast(errorText, job, getManagementLink, docLinks, core) ); } return { completed: completedJobs, failed: failedJobs }; @@ -178,13 +169,13 @@ export class ReportingNotifierStreamHandler { }), catchError((err) => { // show connection refused toast - this.notifications.toasts.addDanger( + this.core.notifications.toasts.addDanger( getGeneralErrorToast( i18n.translate('xpack.reporting.publicNotifier.httpErrorMessage', { defaultMessage: 'Could not check Reporting job status!', }), err, - this.theme + this.core ) ); window.console.error(err); diff --git a/x-pack/plugins/reporting/public/management/mount_management_section.tsx b/x-pack/plugins/reporting/public/management/mount_management_section.tsx index 1e81119439e207..4352557e576173 100644 --- a/x-pack/plugins/reporting/public/management/mount_management_section.tsx +++ b/x-pack/plugins/reporting/public/management/mount_management_section.tsx @@ -10,11 +10,10 @@ import { render, unmountComponentAtNode } from 'react-dom'; import type { CoreStart } from '@kbn/core/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { I18nProvider } from '@kbn/i18n-react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { ManagementAppMountParams } from '@kbn/management-plugin/public'; -import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import type { ClientConfigType } from '@kbn/reporting-public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; import { @@ -44,25 +43,23 @@ export async function mountManagementSection( }; render( - - - - - - - - - - - , + + + + + + + + + , params.element ); diff --git a/x-pack/plugins/reporting/public/mocks.ts b/x-pack/plugins/reporting/public/mocks.ts index 34aa311e548227..b1a447f48a8c0e 100644 --- a/x-pack/plugins/reporting/public/mocks.ts +++ b/x-pack/plugins/reporting/public/mocks.ts @@ -5,6 +5,7 @@ * 2.0. */ +import * as Rx from 'rxjs'; import { coreMock } from '@kbn/core/public/mocks'; import { getSharedComponents } from '@kbn/reporting-public/share'; import { ReportingAPIClient } from '@kbn/reporting-public/reporting_api_client'; @@ -17,7 +18,7 @@ const createSetupContract = (): Setup => { const apiClient = new ReportingAPIClient(coreSetup.http, coreSetup.uiSettings, '7.15.0'); return { usesUiCapabilities: jest.fn().mockImplementation(() => true), - components: getSharedComponents(coreSetup, apiClient), + components: getSharedComponents(apiClient, Rx.from(coreSetup.getStartServices())), }; }; diff --git a/x-pack/plugins/reporting/public/notifier/general_error.tsx b/x-pack/plugins/reporting/public/notifier/general_error.tsx index a3a18edd454d7d..3764c5b6e84784 100644 --- a/x-pack/plugins/reporting/public/notifier/general_error.tsx +++ b/x-pack/plugins/reporting/public/notifier/general_error.tsx @@ -5,16 +5,16 @@ * 2.0. */ -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; import { EuiCallOut, EuiSpacer } from '@elastic/eui'; -import { ThemeServiceStart, ToastInput } from '@kbn/core/public'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { CoreStart, ToastInput } from '@kbn/core/public'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import React from 'react'; export const getGeneralErrorToast = ( errorText: string, err: Error, - theme: ThemeServiceStart + core: CoreStart ): ToastInput => ({ text: toMountPoint( <> @@ -29,7 +29,7 @@ export const getGeneralErrorToast = ( defaultMessage="Try refreshing the page." /> , - { theme$: theme.theme$ } + core ), iconType: undefined, }); diff --git a/x-pack/plugins/reporting/public/notifier/job_failure.tsx b/x-pack/plugins/reporting/public/notifier/job_failure.tsx index e5c6f06413bdfd..c8f44931c29401 100644 --- a/x-pack/plugins/reporting/public/notifier/job_failure.tsx +++ b/x-pack/plugins/reporting/public/notifier/job_failure.tsx @@ -8,8 +8,8 @@ import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; -import { DocLinksStart, ThemeServiceStart, ToastInput } from '@kbn/core/public'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { CoreStart, DocLinksStart, ToastInput } from '@kbn/core/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import * as errors from '@kbn/reporting-common/errors'; import { ManagementLinkFn } from '@kbn/reporting-common/types'; import { sharedI18nTexts } from '../shared_i18n_texts'; @@ -19,8 +19,8 @@ export const getFailureToast = ( errorText: string, job: JobSummary, getManagmenetLink: ManagementLinkFn, - theme: ThemeServiceStart, - docLinks: DocLinksStart + docLinks: DocLinksStart, + core: CoreStart ): ToastInput => { return { title: toMountPoint( @@ -29,7 +29,7 @@ export const getFailureToast = ( defaultMessage="Cannot create {reportType} report for '{reportObjectTitle}'." values={{ reportType: job.jobtype, reportObjectTitle: job.title }} />, - { theme$: theme.theme$ } + core ), text: toMountPoint( <> @@ -60,7 +60,7 @@ export const getFailureToast = ( />

, - { theme$: theme.theme$ } + core ), iconType: undefined, 'data-test-subj': 'completeReportFailure', diff --git a/x-pack/plugins/reporting/public/notifier/job_success.tsx b/x-pack/plugins/reporting/public/notifier/job_success.tsx index 00b08ed2413d90..ae721f675f605a 100644 --- a/x-pack/plugins/reporting/public/notifier/job_success.tsx +++ b/x-pack/plugins/reporting/public/notifier/job_success.tsx @@ -5,9 +5,9 @@ * 2.0. */ -import { ThemeServiceStart, ToastInput } from '@kbn/core/public'; +import { CoreStart, ToastInput } from '@kbn/core/public'; import { FormattedMessage } from '@kbn/i18n-react'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { JobId } from '@kbn/reporting-common/types'; import React from 'react'; import { JobSummary } from '../types'; @@ -18,7 +18,7 @@ export const getSuccessToast = ( job: JobSummary, getReportLink: () => string, getDownloadLink: (jobId: JobId) => string, - theme: ThemeServiceStart + core: CoreStart ): ToastInput => ({ title: toMountPoint( , - { theme$: theme.theme$ } + core ), color: 'success', text: toMountPoint( @@ -36,7 +36,7 @@ export const getSuccessToast = (

, - { theme$: theme.theme$ } + core ), 'data-test-subj': 'completeReportSuccess', }); diff --git a/x-pack/plugins/reporting/public/notifier/job_warning.tsx b/x-pack/plugins/reporting/public/notifier/job_warning.tsx index 6751eb76ab0734..34c73e561f976c 100644 --- a/x-pack/plugins/reporting/public/notifier/job_warning.tsx +++ b/x-pack/plugins/reporting/public/notifier/job_warning.tsx @@ -5,9 +5,9 @@ * 2.0. */ -import { ThemeServiceStart, ToastInput } from '@kbn/core/public'; +import { CoreStart, ToastInput } from '@kbn/core/public'; import { FormattedMessage } from '@kbn/i18n-react'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { JobId } from '@kbn/reporting-common/types'; import React from 'react'; import { JobSummary } from '../types'; @@ -18,7 +18,7 @@ export const getWarningToast = ( job: JobSummary, getReportLink: () => string, getDownloadLink: (jobId: JobId) => string, - theme: ThemeServiceStart + core: CoreStart ): ToastInput => ({ title: toMountPoint( , - { theme$: theme.theme$ } + core ), text: toMountPoint( <> @@ -35,7 +35,7 @@ export const getWarningToast = (

, - { theme$: theme.theme$ } + core ), 'data-test-subj': 'completeReportWarning', }); diff --git a/x-pack/plugins/reporting/public/notifier/job_warning_formulas.tsx b/x-pack/plugins/reporting/public/notifier/job_warning_formulas.tsx index 4cf9f3f655cc1f..2f8c39b9046663 100644 --- a/x-pack/plugins/reporting/public/notifier/job_warning_formulas.tsx +++ b/x-pack/plugins/reporting/public/notifier/job_warning_formulas.tsx @@ -7,9 +7,9 @@ import React from 'react'; -import { ThemeServiceStart, ToastInput } from '@kbn/core/public'; +import { CoreStart, ToastInput } from '@kbn/core/public'; import { FormattedMessage } from '@kbn/i18n-react'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { JobId } from '@kbn/reporting-common/types'; import { DownloadButton } from './job_download_button'; @@ -20,7 +20,7 @@ export const getWarningFormulasToast = ( job: JobSummary, getReportLink: () => string, getDownloadLink: (jobId: JobId) => string, - theme: ThemeServiceStart + core: CoreStart ): ToastInput => ({ title: toMountPoint( , - { theme$: theme.theme$ } + core ), text: toMountPoint( <> @@ -44,7 +44,7 @@ export const getWarningFormulasToast = (

, - { theme$: theme.theme$ } + core ), 'data-test-subj': 'completeReportCsvFormulasWarning', }); diff --git a/x-pack/plugins/reporting/public/notifier/job_warning_max_size.tsx b/x-pack/plugins/reporting/public/notifier/job_warning_max_size.tsx index 54c76282420676..b87547669d704a 100644 --- a/x-pack/plugins/reporting/public/notifier/job_warning_max_size.tsx +++ b/x-pack/plugins/reporting/public/notifier/job_warning_max_size.tsx @@ -5,9 +5,9 @@ * 2.0. */ -import { ThemeServiceStart, ToastInput } from '@kbn/core/public'; +import { CoreStart, ToastInput } from '@kbn/core/public'; import { FormattedMessage } from '@kbn/i18n-react'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import type { JobId } from '@kbn/reporting-common/types'; import React from 'react'; import { JobSummary } from '../types'; @@ -18,7 +18,7 @@ export const getWarningMaxSizeToast = ( job: JobSummary, getReportLink: () => string, getDownloadLink: (jobId: JobId) => string, - theme: ThemeServiceStart + core: CoreStart ): ToastInput => ({ title: toMountPoint( , - { theme$: theme.theme$ } + core ), text: toMountPoint( <> @@ -41,7 +41,7 @@ export const getWarningMaxSizeToast = (

, - { theme$: theme.theme$ } + core ), 'data-test-subj': 'completeReportMaxSizeWarning', }); diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index dddf18003fb946..cb606ca35152f5 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -5,16 +5,9 @@ * 2.0. */ -import { from, ReplaySubject } from 'rxjs'; +import { from, map, type Observable, ReplaySubject } from 'rxjs'; -import { - CoreSetup, - CoreStart, - HttpSetup, - IUiSettingsClient, - Plugin, - PluginInitializerContext, -} from '@kbn/core/public'; +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public'; import type { HomePublicPluginSetup, HomePublicPluginStart } from '@kbn/home-plugin/public'; @@ -39,6 +32,7 @@ import { import { ReportingCsvPanelAction } from '@kbn/reporting-csv-share-panel'; import type { ReportingSetup, ReportingStart } from '.'; import { ReportingNotifierStreamHandler as StreamHandler } from './lib/stream_handler'; +import { StartServices } from './types'; export interface ReportingPublicPluginSetupDependencies { home: HomePublicPluginSetup; @@ -57,6 +51,8 @@ export interface ReportingPublicPluginStartDependencies { share: SharePluginStart; } +type StartServices$ = Observable; + /** * @internal * @implements Plugin @@ -81,29 +77,18 @@ export class ReportingPublicPlugin }); private config: ClientConfigType; private contract?: ReportingSetup; + private startServices$?: StartServices$; constructor(initializerContext: PluginInitializerContext) { this.config = initializerContext.config.get(); this.kibanaVersion = initializerContext.env.packageInfo.version; } - /* - * Use a single instance of ReportingAPIClient for all the reporting code - */ - private getApiClient(http: HttpSetup, uiSettings: IUiSettingsClient) { - if (!this.apiClient) { - this.apiClient = new ReportingAPIClient(http, uiSettings, this.kibanaVersion); - } - return this.apiClient; - } - - private getContract(core?: CoreSetup) { - if (core) { - this.contract = { - usesUiCapabilities: () => this.config.roles?.enabled === false, - components: getSharedComponents(core, this.getApiClient(core.http, core.uiSettings)), - }; - } + private getContract(apiClient: ReportingAPIClient, startServices$: StartServices$) { + this.contract = { + usesUiCapabilities: () => this.config.roles?.enabled === false, + components: getSharedComponents(apiClient, startServices$), + }; if (!this.contract) { throw new Error(`Setup error in Reporting plugin!`); @@ -116,7 +101,7 @@ export class ReportingPublicPlugin core: CoreSetup, setupDeps: ReportingPublicPluginSetupDependencies ) { - const { getStartServices, uiSettings } = core; + const { getStartServices } = core; const { home: homeSetup, management: managementSetup, @@ -125,10 +110,25 @@ export class ReportingPublicPlugin uiActions: uiActionsSetup, } = setupDeps; - const startServices$ = from(getStartServices()); + const startServices$: Observable = from(getStartServices()).pipe( + map(([services, ...rest]) => { + return [ + { + application: services.application, + analytics: services.analytics, + i18n: services.i18n, + theme: services.theme, + notifications: services.notifications, + uiSettings: services.uiSettings, + }, + ...rest, + ]; + }) + ); const usesUiCapabilities = !this.config.roles.enabled; - const apiClient = this.getApiClient(core.http, core.uiSettings); + const apiClient = new ReportingAPIClient(core.http, core.uiSettings, this.kibanaVersion); + this.apiClient = apiClient; homeSetup.featureCatalogue.register({ id: 'reporting', @@ -204,20 +204,15 @@ export class ReportingPublicPlugin }) ); - const reportingStart = this.getContract(core); - const { toasts } = core.notifications; - - startServices$.subscribe(([{ application, i18n: i18nStart }, { licensing }]) => { + startServices$.subscribe(([{ application }, { licensing }]) => { licensing.license$.subscribe((license) => { shareSetup.register( reportingCsvShareProvider({ apiClient, - toasts, - uiSettings, license, application, usesUiCapabilities, - theme: core.theme, + startServices$, }) ); if (this.config.export_types.pdf.enabled || this.config.export_types.png.enabled) { @@ -225,12 +220,10 @@ export class ReportingPublicPlugin shareSetup.register( reportingScreenshotShareProvider({ apiClient, - toasts, - uiSettings, license, application, usesUiCapabilities, - theme: core.theme, + startServices$, }) ); } @@ -238,12 +231,10 @@ export class ReportingPublicPlugin shareSetup.register( reportingCsvShareModalProvider({ apiClient, - uiSettings, license, application, usesUiCapabilities, - theme: core.theme, - i18n: i18nStart, + startServices$, }) ); @@ -251,29 +242,27 @@ export class ReportingPublicPlugin shareSetup.register( reportingExportModalProvider({ apiClient, - uiSettings, license, application, usesUiCapabilities, - theme: core.theme, - i18n: i18nStart, + startServices$, }) ); } } }); }); - return reportingStart; + + this.startServices$ = startServices$; + return this.getContract(apiClient, startServices$); } public start(core: CoreStart) { - const { notifications, docLinks } = core; - const apiClient = this.getApiClient(core.http, core.uiSettings); - const streamHandler = new StreamHandler(notifications, apiClient, core.theme, docLinks); + const streamHandler = new StreamHandler(this.apiClient!, core); const interval = durationToNumber(this.config.poll.jobsRefresh.interval); streamHandler.startPolling(interval, this.stop$); - return this.getContract(); + return this.getContract(this.apiClient!, this.startServices$!); } public stop() { diff --git a/x-pack/plugins/reporting/public/types.ts b/x-pack/plugins/reporting/public/types.ts index d5af032db617ce..9ba50435471ab2 100644 --- a/x-pack/plugins/reporting/public/types.ts +++ b/x-pack/plugins/reporting/public/types.ts @@ -5,8 +5,29 @@ * 2.0. */ +import type { CoreStart } from '@kbn/core/public'; import { JOB_STATUS } from '@kbn/reporting-common'; import type { JobId, ReportOutput, ReportSource, TaskRunResult } from '@kbn/reporting-common/types'; +import { ReportingPublicPluginStartDependencies } from './plugin'; + +/* + * Required services for mounting React components + */ +export type StartServices = [ + Pick< + CoreStart, + // required for modules that render React + | 'analytics' + | 'i18n' + | 'theme' + // used extensively in Reporting plugin + | 'application' + | 'notifications' + | 'uiSettings' + >, + ReportingPublicPluginStartDependencies, + unknown +]; /* * Notifier Toasts diff --git a/x-pack/plugins/reporting/tsconfig.json b/x-pack/plugins/reporting/tsconfig.json index 867233ad463b0c..e0f781d28f62cc 100644 --- a/x-pack/plugins/reporting/tsconfig.json +++ b/x-pack/plugins/reporting/tsconfig.json @@ -43,13 +43,14 @@ "@kbn/reporting-export-types-png", "@kbn/reporting-export-types-pdf-common", "@kbn/reporting-export-types-csv-common", - "@kbn/react-kibana-context-theme", "@kbn/reporting-export-types-png-common", "@kbn/reporting-mocks-server", "@kbn/core-http-request-handler-context-server", "@kbn/reporting-public", "@kbn/analytics-client", "@kbn/reporting-csv-share-panel", + "@kbn/react-kibana-context-render", + "@kbn/react-kibana-mount", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/search_playground/public/application.tsx b/x-pack/plugins/search_playground/public/application.tsx index f76a30a22ae87f..ef132487a493bd 100644 --- a/x-pack/plugins/search_playground/public/application.tsx +++ b/x-pack/plugins/search_playground/public/application.tsx @@ -8,7 +8,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { CoreStart, AppMountParameters } from '@kbn/core/public'; -import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { I18nProvider } from '@kbn/i18n-react'; import { BrowserRouter as Router } from '@kbn/shared-ux-router'; @@ -27,7 +27,7 @@ export const renderApp = ( const navigation = services.navigation; ReactDOM.render( - + @@ -57,7 +57,7 @@ export const renderApp = ( - , + , element ); diff --git a/x-pack/plugins/search_playground/tsconfig.json b/x-pack/plugins/search_playground/tsconfig.json index 8bcef27ddb8f86..c4cfb17e288707 100644 --- a/x-pack/plugins/search_playground/tsconfig.json +++ b/x-pack/plugins/search_playground/tsconfig.json @@ -20,7 +20,6 @@ "@kbn/ml-response-stream", "@kbn/security-plugin", "@kbn/user-profile-components", - "@kbn/react-kibana-context-theme", "@kbn/shared-ux-router", "@kbn/shared-ux-page-kibana-template", "@kbn/navigation-plugin", @@ -34,7 +33,8 @@ "@kbn/cases-plugin", "@kbn/triggers-actions-ui-plugin", "@kbn/elastic-assistant-common", - "@kbn/logging" + "@kbn/logging", + "@kbn/react-kibana-context-render" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/is_response_action_supported.ts b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/is_response_action_supported.ts index 31dd195f00e03a..d197995de90d83 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/is_response_action_supported.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/is_response_action_supported.ts @@ -5,10 +5,7 @@ * 2.0. */ -import { getRbacControl } from './utils'; -import type { EndpointPrivileges } from '../../types'; import { - RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP, type ResponseActionAgentType, type ResponseActionsApiCommandNames, type ResponseActionType, @@ -19,65 +16,6 @@ type SupportMap = Record< Record> >; -/** @private */ -const getResponseActionsSupportMap = ({ - agentType, - actionName, - actionType, - privileges, -}: { - agentType: ResponseActionAgentType; - actionName: ResponseActionsApiCommandNames; - actionType: ResponseActionType; - privileges: EndpointPrivileges; -}): boolean => { - const commandName = RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP[actionName]; - const RESPONSE_ACTIONS_SUPPORT_MAP = { - [actionName]: { - automated: { - [agentType]: - agentType === 'endpoint' - ? getRbacControl({ - commandName, - privileges, - }) - : false, - }, - manual: { - [agentType]: - agentType === 'endpoint' - ? getRbacControl({ - commandName, - privileges, - }) - : actionName === 'isolate' || actionName === 'unisolate', - }, - }, - } as SupportMap; - return RESPONSE_ACTIONS_SUPPORT_MAP[actionName][actionType][agentType]; -}; - -/** - * Determine if a given response action is currently supported - * @param agentType - * @param actionName - * @param actionType - * @param privileges - */ -export const isResponseActionSupported = ( - agentType: ResponseActionAgentType, - actionName: ResponseActionsApiCommandNames, - actionType: ResponseActionType, - privileges: EndpointPrivileges -): boolean => { - return getResponseActionsSupportMap({ - privileges, - actionName, - actionType, - agentType, - }); -}; - /** @private */ const RESPONSE_ACTIONS_SUPPORT_MAP: SupportMap = { isolate: { @@ -162,7 +100,12 @@ const RESPONSE_ACTIONS_SUPPORT_MAP: SupportMap = { }, }; -// FIXME:PT reemove once this module is refactored. +/** + * Check if a given Response action is supported (implemented) for a given agent type and action type + * @param agentType + * @param actionName + * @param actionType + */ export const isActionSupportedByAgentType = ( agentType: ResponseActionAgentType, actionName: ResponseActionsApiCommandNames, diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 6004c15b222c59..edf11805a4de5b 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -80,8 +80,9 @@ export const allowedExperimentalValues = Object.freeze({ responseActionsSentinelOneV1Enabled: true, /** - * Enables use of SentinelOne response actions that complete asynchronously as well as support - * for more response actions. + * Enables use of SentinelOne response actions that complete asynchronously + * + * Release: v8.14.0 */ responseActionsSentinelOneV2Enabled: false, @@ -200,7 +201,9 @@ export const allowedExperimentalValues = Object.freeze({ sentinelOneDataInAnalyzerEnabled: true, /** - * Enables SentinelOne manual host manipulation actions + * Enables SentinelOne manual host isolation response actions directly through the connector + * sub-actions framework. + * v8.12.0 */ sentinelOneManualHostActionsEnabled: true, diff --git a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts b/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts index fb20548271191e..5e17f3178d59c2 100644 --- a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts +++ b/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts @@ -10,7 +10,10 @@ import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { getSentinelOneAgentId } from '../../../common/utils/sentinelone_alert_check'; import type { ThirdPartyAgentInfo } from '../../../../common/types'; -import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants'; +import type { + ResponseActionAgentType, + EndpointCapabilities, +} from '../../../../common/endpoint/service/response_actions/constants'; import { useGetEndpointDetails, useWithShowResponder } from '../../../management/hooks'; import { HostStatus } from '../../../../common/endpoint/types'; import { @@ -144,7 +147,7 @@ export const useResponderActionData = ({ showResponseActionsConsole({ agentId: hostInfo.metadata.agent.id, agentType: 'endpoint', - capabilities: hostInfo.metadata.Endpoint.capabilities ?? [], + capabilities: (hostInfo.metadata.Endpoint.capabilities as EndpointCapabilities[]) ?? [], hostName: hostInfo.metadata.host.name, }); } diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx index 75c57f6ad43c4f..90e44c4a56ee2a 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx @@ -25,9 +25,11 @@ export const GetFileActionResult = memo< const actionRequestBody = useMemo(() => { const endpointId = command.commandDefinition?.meta?.endpointId; const { path, comment } = command.args.args; + const agentType = command.commandDefinition?.meta?.agentType; return endpointId ? { + agent_type: agentType, endpoint_ids: [endpointId], comment: comment?.[0], parameters: { @@ -35,7 +37,11 @@ export const GetFileActionResult = memo< }, } : undefined; - }, [command.args.args, command.commandDefinition?.meta?.endpointId]); + }, [ + command.args.args, + command.commandDefinition?.meta?.agentType, + command.commandDefinition?.meta?.endpointId, + ]); const { result, actionDetails } = useConsoleActionSubmitter({ ResultComponent, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_file_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_file_action.test.tsx index 9c6e49818daf67..06148aed5b483f 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_file_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_file_action.test.tsx @@ -14,6 +14,7 @@ import { ConsoleManagerTestComponent, getConsoleManagerMockRenderResultQueriesAndActions, } from '../../../console/components/console_manager/mocks'; +import type { GetEndpointConsoleCommandsOptions } from '../../lib/console_commands_definition'; import { getEndpointConsoleCommands } from '../../lib/console_commands_definition'; import React from 'react'; import { enterConsoleCommand } from '../../../console/mocks'; @@ -33,7 +34,6 @@ import type { HttpFetchOptionsWithPath } from '@kbn/core-http-browser'; import { endpointActionResponseCodes } from '../../lib/endpoint_action_response_codes'; jest.mock('../../../../../common/components/user_privileges'); -jest.mock('../../../../../common/experimental_features_service'); describe('When using get-file action from response actions console', () => { let render: ( @@ -45,13 +45,22 @@ describe('When using get-file action from response actions console', () => { typeof getConsoleManagerMockRenderResultQueriesAndActions >; let endpointPrivileges: EndpointPrivileges; + let getConsoleCommandsOptions: GetEndpointConsoleCommandsOptions; + let mockedContext: AppContextTestRender; beforeEach(() => { - const mockedContext = createAppRootMockRenderer(); + mockedContext = createAppRootMockRenderer(); apiMocks = responseActionsHttpMocks(mockedContext.coreStart.http); endpointPrivileges = { ...getEndpointAuthzInitialStateMock(), loading: false }; + getConsoleCommandsOptions = { + agentType: 'endpoint', + endpointAgentId: 'a.b.c', + endpointCapabilities: [...ENDPOINT_CAPABILITIES], + endpointPrivileges, + }; + render = async (capabilities: EndpointCapabilities[] = [...ENDPOINT_CAPABILITIES]) => { renderResult = mockedContext.render( { consoleProps: { 'data-test-subj': 'test', commands: getEndpointConsoleCommands({ - agentType: 'endpoint', - endpointAgentId: 'a.b.c', - endpointCapabilities: [...capabilities], - endpointPrivileges, + ...getConsoleCommandsOptions, + endpointCapabilities: capabilities, }), }, }; @@ -123,7 +130,7 @@ describe('When using get-file action from response actions console', () => { await waitFor(() => { expect(apiMocks.responseProvider.getFile).toHaveBeenCalledWith({ - body: '{"endpoint_ids":["a.b.c"],"parameters":{"path":"one/two"}}', + body: '{"agent_type":"endpoint","endpoint_ids":["a.b.c"],"parameters":{"path":"one/two"}}', path: GET_FILE_ROUTE, version: '2023-10-31', }); @@ -204,4 +211,57 @@ describe('When using get-file action from response actions console', () => { ); }); }); + + describe('And agent type is SentinelOne', () => { + beforeEach(() => { + getConsoleCommandsOptions.agentType = 'sentinel_one'; + mockedContext.setExperimentalFlag({ + responseActionsSentinelOneGetFileEnabled: true, + }); + }); + + it('should display error if feature flag is not enabled', async () => { + mockedContext.setExperimentalFlag({ + responseActionsSentinelOneGetFileEnabled: false, + }); + await render(); + enterConsoleCommand(renderResult, 'get-file --path="one/two"'); + + expect(renderResult.getByTestId('test-validationError-message').textContent).toEqual( + UPGRADE_AGENT_FOR_RESPONDER('sentinel_one', 'get-file') + ); + }); + + it('should call API with `agent_type` set to `sentinel_one`', async () => { + await render(); + enterConsoleCommand(renderResult, 'get-file --path="one/two"'); + + await waitFor(() => { + expect(apiMocks.responseProvider.getFile).toHaveBeenCalledWith({ + body: '{"agent_type":"sentinel_one","endpoint_ids":["a.b.c"],"parameters":{"path":"one/two"}}', + path: GET_FILE_ROUTE, + version: '2023-10-31', + }); + }); + }); + + it('should not look at `capabilities` to determine compatibility', async () => { + await render([]); + enterConsoleCommand(renderResult, 'get-file --path="one/two"'); + + await waitFor(() => { + expect(apiMocks.responseProvider.getFile).toHaveBeenCalled(); + }); + expect(renderResult.queryByTestId('test-validationError-message')).toBeNull(); + }); + + it('should display pending message', async () => { + await render(); + enterConsoleCommand(renderResult, 'get-file --path="one/two"'); + + await waitFor(() => { + expect(renderResult.getByTestId('getFile-pending')); + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/isolate_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/isolate_action.test.tsx index 77e70fc14180ce..f5a20be31580a1 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/isolate_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/isolate_action.test.tsx @@ -24,8 +24,7 @@ import { UPGRADE_AGENT_FOR_RESPONDER } from '../../../../../common/translations' jest.mock('../../../../../common/experimental_features_service'); -// FLAKY https://github.com/elastic/kibana/issues/145363 -describe.skip('When using isolate action from response actions console', () => { +describe('When using isolate action from response actions console', () => { let render: ( capabilities?: EndpointCapabilities[] ) => Promise>; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts index 238efec7542dca..61fb75cb524502 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import { isActionSupportedByAgentType } from '../../../../../common/endpoint/service/response_actions/is_response_action_supported'; import { getRbacControl } from '../../../../../common/endpoint/service/response_actions/utils'; import { UploadActionResult } from '../command_render_components/upload_action'; import { ArgumentFileSelector } from '../../console_argument_selectors'; @@ -16,7 +17,10 @@ import type { EndpointCapabilities, ResponseActionAgentType, } from '../../../../../common/endpoint/service/response_actions/constants'; -import { RESPONSE_CONSOLE_ACTION_COMMANDS_TO_ENDPOINT_CAPABILITY } from '../../../../../common/endpoint/service/response_actions/constants'; +import { + RESPONSE_CONSOLE_ACTION_COMMANDS_TO_ENDPOINT_CAPABILITY, + RESPONSE_CONSOLE_COMMAND_TO_API_COMMAND_MAP, +} from '../../../../../common/endpoint/service/response_actions/constants'; import { GetFileActionResult } from '../command_render_components/get_file_action'; import type { Command, CommandDefinition } from '../../console'; import { IsolateActionResult } from '../command_render_components/isolate_action'; @@ -83,14 +87,18 @@ const capabilitiesAndPrivilegesValidator = ( const responderCapability = RESPONSE_CONSOLE_ACTION_COMMANDS_TO_ENDPOINT_CAPABILITY[commandName]; let errorMessage = ''; - if (!responderCapability) { - errorMessage = errorMessage.concat(UPGRADE_AGENT_FOR_RESPONDER(agentType, commandName)); - } - if (responderCapability) { - if (!agentCapabilities.includes(responderCapability)) { + + // We only validate Agent capabilities for the command for Endpoint agents + if (agentType === 'endpoint') { + if (!responderCapability) { + errorMessage = errorMessage.concat(UPGRADE_AGENT_FOR_RESPONDER(agentType, commandName)); + } + + if (responderCapability && !agentCapabilities.includes(responderCapability)) { errorMessage = errorMessage.concat(UPGRADE_AGENT_FOR_RESPONDER(agentType, commandName)); } } + if (!getRbacControl({ commandName, privileges })) { errorMessage = errorMessage.concat(INSUFFICIENT_PRIVILEGES_FOR_COMMAND); } @@ -127,27 +135,36 @@ const COMMENT_ARG_ABOUT = i18n.translate( { defaultMessage: 'A comment to go along with the action' } ); +export interface GetEndpointConsoleCommandsOptions { + endpointAgentId: string; + agentType: ResponseActionAgentType; + endpointCapabilities: ImmutableArray; + endpointPrivileges: EndpointPrivileges; +} + export const getEndpointConsoleCommands = ({ endpointAgentId, agentType, endpointCapabilities, endpointPrivileges, -}: { - endpointAgentId: string; - agentType: ResponseActionAgentType; - endpointCapabilities: ImmutableArray; - endpointPrivileges: EndpointPrivileges; -}): CommandDefinition[] => { +}: GetEndpointConsoleCommandsOptions): CommandDefinition[] => { const featureFlags = ExperimentalFeaturesService.get(); const isUploadEnabled = featureFlags.responseActionUploadEnabled; const doesEndpointSupportCommand = (commandName: ConsoleResponseActionCommands) => { + // Agent capabilities is only validated for Endpoint agent types + if (agentType !== 'endpoint') { + return true; + } + const responderCapability = RESPONSE_CONSOLE_ACTION_COMMANDS_TO_ENDPOINT_CAPABILITY[commandName]; + if (responderCapability) { return endpointCapabilities.includes(responderCapability); } + return false; }; @@ -484,5 +501,54 @@ export const getEndpointConsoleCommands = ({ }); } - return consoleCommands; + switch (agentType) { + case 'sentinel_one': + return adjustCommandsForSentinelOne({ commandList: consoleCommands }); + default: + // agentType === endpoint: just returns the defined command list + return consoleCommands; + } +}; + +/** @private */ +const adjustCommandsForSentinelOne = ({ + commandList, +}: { + commandList: CommandDefinition[]; +}): CommandDefinition[] => { + const featureFlags = ExperimentalFeaturesService.get(); + const isHostIsolationEnabled = featureFlags.responseActionsSentinelOneV1Enabled; + const isGetFileFeatureEnabled = featureFlags.responseActionsSentinelOneGetFileEnabled; + + const disableCommand = (command: CommandDefinition) => { + command.helpDisabled = true; + command.helpHidden = true; + command.validate = () => + UPGRADE_AGENT_FOR_RESPONDER('sentinel_one', command.name as ConsoleResponseActionCommands); + }; + + return commandList.map((command) => { + const agentSupportsResponseAction = + command.name === 'status' + ? false + : isActionSupportedByAgentType( + 'sentinel_one', + RESPONSE_CONSOLE_COMMAND_TO_API_COMMAND_MAP[ + command.name as ConsoleResponseActionCommands + ], + 'manual' + ); + + // If command is not supported by SentinelOne - disable it + if ( + !agentSupportsResponseAction || + (command.name === 'get-file' && !isGetFileFeatureEnabled) || + (command.name === 'isolate' && !isHostIsolationEnabled) || + (command.name === 'release' && !isHostIsolationEnabled) + ) { + disableCommand(command); + } + + return command; + }); }; diff --git a/x-pack/plugins/security_solution/public/management/hooks/use_with_show_responder.tsx b/x-pack/plugins/security_solution/public/management/hooks/use_with_show_responder.tsx index c36b02d90ccf0f..15186ddc486f31 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/use_with_show_responder.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/use_with_show_responder.tsx @@ -7,19 +7,11 @@ import React, { useCallback } from 'react'; import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { - TECHNICAL_PREVIEW, - TECHNICAL_PREVIEW_TOOLTIP, - UPGRADE_AGENT_FOR_RESPONDER, -} from '../../common/translations'; +import { TECHNICAL_PREVIEW, TECHNICAL_PREVIEW_TOOLTIP } from '../../common/translations'; import { useLicense } from '../../common/hooks/use_license'; -import type { ImmutableArray } from '../../../common/endpoint/types'; -import { - type ConsoleResponseActionCommands, - RESPONSE_CONSOLE_COMMAND_TO_API_COMMAND_MAP, - type ResponseActionAgentType, -} from '../../../common/endpoint/service/response_actions/constants'; -import { isResponseActionSupported } from '../../../common/endpoint/service/response_actions/is_response_action_supported'; +import type { MaybeImmutable } from '../../../common/endpoint/types'; +import type { EndpointCapabilities } from '../../../common/endpoint/service/response_actions/constants'; +import { type ResponseActionAgentType } from '../../../common/endpoint/service/response_actions/constants'; import { HeaderSentinelOneInfo } from '../components/endpoint_responder/components/header_info/sentinel_one/header_sentinel_one_info'; import { useUserPrivileges } from '../../common/components/user_privileges'; @@ -39,16 +31,16 @@ type ShowResponseActionsConsole = (props: ResponderInfoProps) => void; export interface BasicConsoleProps { agentId: string; hostName: string; + /** Required for Endpoint agents. */ + capabilities: MaybeImmutable; } type ResponderInfoProps = | (BasicConsoleProps & { agentType: Extract; - capabilities: ImmutableArray; }) | (BasicConsoleProps & { agentType: Exclude; - capabilities: ImmutableArray; platform: string; }); @@ -85,33 +77,6 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => { endpointAgentId: agentId, endpointCapabilities: capabilities, endpointPrivileges, - }).map((command) => { - if (command.name !== 'status') { - return { - ...command, - helpHidden: !isResponseActionSupported( - agentType, - RESPONSE_CONSOLE_COMMAND_TO_API_COMMAND_MAP[ - command.name as ConsoleResponseActionCommands - ], - 'manual', - endpointPrivileges - ), - }; - } else if (agentType !== 'endpoint') { - // do not show 'status' for non-endpoint agents - return { - ...command, - helpHidden: true, - validate: () => { - return UPGRADE_AGENT_FOR_RESPONDER( - agentType, - command.name as ConsoleResponseActionCommands - ); - }, - }; - } - return command; }), 'data-test-subj': `${agentType}ResponseActionsConsole`, storagePrefix: 'xpack.securitySolution.Responder', @@ -138,6 +103,7 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => { meta: { agentId, hostName, + capabilities, }, consoleProps, PageTitleComponent: () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx index 6f18c60d4dc6b1..1bd0c3dff62ff0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx @@ -8,6 +8,7 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { pagePathGetters } from '@kbn/fleet-plugin/public'; +import type { EndpointCapabilities } from '../../../../../../common/endpoint/service/response_actions/constants'; import { useUserPrivileges } from '../../../../../common/components/user_privileges'; import { useWithShowResponder } from '../../../../hooks'; import { APP_UI_ID } from '../../../../../../common/constants'; @@ -130,7 +131,8 @@ export const useEndpointActionItems = ( showEndpointResponseActionsConsole({ agentId: endpointMetadata.agent.id, agentType: 'endpoint', - capabilities: endpointMetadata.Endpoint.capabilities ?? [], + capabilities: + (endpointMetadata.Endpoint.capabilities as EndpointCapabilities[]) ?? [], hostName: endpointMetadata.host.name, }); }, diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index 2c5ca1e049fae1..8c3f13eefffce1 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -28,42 +28,9 @@ import { exec } from 'child_process'; import { renderSummaryTable } from './print_run'; import { parseTestFileConfig, retrieveIntegrations } from './utils'; -interface ProductType { - product_line: string; - product_tier: string; -} - -interface OverrideEntry { - docker_image: string; -} - -interface ProductOverrides { - kibana?: OverrideEntry; - elasticsearch?: OverrideEntry; - fleet?: OverrideEntry; - cluster?: OverrideEntry; -} - -interface CreateProjectRequestBody { - name: string; - region_id: string; - product_types?: ProductType[]; - overrides?: ProductOverrides; -} - -interface Project { - name: string; - id: string; - region: string; - es_url: string; - kb_url: string; - product: string; -} - -interface Credentials { - username: string; - password: string; -} +import type { ProductType, Credentials, ProjectHandler } from './project_handler/project_handler'; +import { CloudHandler } from './project_handler/cloud_project_handler'; +import { ProxyHandler } from './project_handler/proxy_project_handler'; const DEFAULT_CONFIGURATION: Readonly = [ { product_line: 'security', product_tier: 'complete' }, @@ -71,7 +38,6 @@ const DEFAULT_CONFIGURATION: Readonly = [ { product_line: 'endpoint', product_tier: 'complete' }, ] as const; -const DEFAULT_REGION = 'aws-eu-west-1'; const PROJECT_NAME_PREFIX = 'kibana-cypress-security-solution-ephemeral'; const BASE_ENV_URL = `${process.env.QA_CONSOLE_URL}`; let log: ToolingLog; @@ -96,156 +62,27 @@ const getApiKeyFromElasticCloudJsonFile = (): string | undefined => { } }; -// Method to invoke the create project API for serverless. -async function createSecurityProject( - projectName: string, - apiKey: string, - productTypes: ProductType[], - commit: string -): Promise { - const body: CreateProjectRequestBody = { - name: projectName, - region_id: DEFAULT_REGION, - product_types: productTypes, - }; - - log.info(`Kibana override flag equals to ${process.env.KIBANA_MKI_USE_LATEST_COMMIT}!`); - if ( - (process.env.KIBANA_MKI_USE_LATEST_COMMIT && - process.env.KIBANA_MKI_USE_LATEST_COMMIT === '1') || - commit - ) { - const override = commit ? commit : process.env.BUILDKITE_COMMIT; - const kibanaOverrideImage = `${override?.substring(0, 12)}`; - log.info( - `Overriding Kibana image in the MKI with docker.elastic.co/kibana-ci/kibana-serverless:sec-sol-qg-${kibanaOverrideImage}` - ); - body.overrides = { - kibana: { - docker_image: `docker.elastic.co/kibana-ci/kibana-serverless:sec-sol-qg-${kibanaOverrideImage}`, - }, - }; - } - - try { - const response = await axios.post(`${BASE_ENV_URL}/api/v1/serverless/projects/security`, body, { - headers: { - Authorization: `ApiKey ${apiKey}`, - }, - }); - return { - name: response.data.name, - id: response.data.id, - region: response.data.region_id, - es_url: `${response.data.endpoints.elasticsearch}:443`, - kb_url: `${response.data.endpoints.kibana}:443`, - product: response.data.type, - }; - } catch (error) { - if (error instanceof AxiosError) { - const errorData = JSON.stringify(error.response?.data); - log.error(`${error.response?.status}:${errorData}`); - } else { - log.error(`${error.message}`); - } - } -} - -// Method to invoke the delete project API for serverless. -async function deleteSecurityProject( - projectId: string, - projectName: string, - apiKey: string -): Promise { - try { - await axios.delete(`${BASE_ENV_URL}/api/v1/serverless/projects/security/${projectId}`, { - headers: { - Authorization: `ApiKey ${apiKey}`, - }, - }); - log.info(`Project ${projectName} was successfully deleted!`); - } catch (error) { - if (error instanceof AxiosError) { - log.error(`${error.response?.status}:${error.response?.data}`); - } else { - log.error(`${error.message}`); - } - } -} +// Check if proxy service is up and running executing a healthcheck call. +function proxyHealthcheck(proxyUrl: string): Promise { + const fetchHealthcheck = async (attemptNum: number) => { + log.info(`Retry number ${attemptNum} to check if Elasticsearch is green.`); -// Method to reset the credentials for the created project. -async function resetCredentials( - projectId: string, - runnerId: string, - apiKey: string -): Promise { - log.info(`${runnerId} : Reseting credentials`); - - const fetchResetCredentialsStatusAttempt = async (attemptNum: number) => { - const response = await axios.post( - `${BASE_ENV_URL}/api/v1/serverless/projects/security/${projectId}/_reset-internal-credentials`, - {}, - { - headers: { - Authorization: `ApiKey ${apiKey}`, - }, - } - ); - log.info('Credentials have ben reset'); - return { - password: response.data.password, - username: response.data.username, - }; + const response = await axios.get(`${proxyUrl}/healthcheck`); + log.info(`The proxy service is available.`); + return response.status === 200; }; - const retryOptions = { onFailedAttempt: (error: Error | AxiosError) => { - if (error instanceof AxiosError && error.code === 'ENOTFOUND') { - log.info('Project is not reachable. A retry will be triggered soon..'); - } else { - log.error(`${error.message}`); + if (error instanceof AxiosError) { + log.info(`The proxy service is not available. A retry will be triggered soon...`); } }, - retries: 100, + retries: 4, factor: 2, maxTimeout: 20000, }; - return pRetry(fetchResetCredentialsStatusAttempt, retryOptions); -} - -// Wait until Project is initialized -function waitForProjectInitialized(projectId: string, apiKey: string): Promise { - const fetchProjectStatusAttempt = async (attemptNum: number) => { - log.info(`Retry number ${attemptNum} to check if project is initialized.`); - const response = await axios.get( - `${BASE_ENV_URL}/api/v1/serverless/projects/security/${projectId}/status`, - { - headers: { - Authorization: `ApiKey ${apiKey}`, - }, - } - ); - if (response.data.phase !== 'initialized') { - log.info(response.data); - throw new Error('Project is not initialized. A retry will be triggered soon...'); - } else { - log.info('Project is initialized'); - } - }; - const retryOptions = { - onFailedAttempt: (error: Error | AxiosError) => { - if (error instanceof AxiosError && error.code === 'ENOTFOUND') { - log.info('Project is not reachable. A retry will be triggered soon...'); - } else { - log.error(`${error.message}`); - } - }, - retries: 100, - factor: 2, - maxTimeout: 20000, - }; - return pRetry(fetchProjectStatusAttempt, retryOptions); + return pRetry(fetchHealthcheck, retryOptions); } // Wait until elasticsearch status goes green @@ -406,10 +243,25 @@ export const cli = () => { return process.exit(1); } + const PROXY_URL = process.env.PROXY_URL ? process.env.PROXY_URL : undefined; + const PROXY_SECRET = process.env.PROXY_SECRET ? process.env.PROXY_SECRET : undefined; + const PROXY_CLIENT_ID = process.env.PROXY_CLIENT_ID ? process.env.PROXY_CLIENT_ID : undefined; + const API_KEY = process.env.CLOUD_QA_API_KEY ? process.env.CLOUD_QA_API_KEY : getApiKeyFromElasticCloudJsonFile(); + let cloudHandler: ProjectHandler; + if (PROXY_URL && PROXY_CLIENT_ID && PROXY_SECRET && (await proxyHealthcheck(PROXY_URL))) { + cloudHandler = new ProxyHandler(PROXY_URL, PROXY_CLIENT_ID, PROXY_SECRET); + } else if (API_KEY) { + cloudHandler = new CloudHandler(API_KEY, BASE_ENV_URL); + } else { + log.info('PROXY_URL or API KEY which are needed to create project could not be retrieved.'); + // eslint-disable-next-line no-process-exit + return process.exit(1); + } + const PARALLEL_COUNT = process.env.PARALLEL_COUNT ? Number(process.env.PARALLEL_COUNT) : 1; if (!process.env.CLOUD_ENV) { @@ -417,7 +269,6 @@ export const cli = () => { 'The cloud environment to be provided with the env var CLOUD_ENV. Currently working only for QA so the script can proceed.' ); // Abort when more environments will be integrated - // return process.exit(0); } @@ -469,15 +320,16 @@ ${JSON.stringify(argv, null, 2)} const isOpen = argv._.includes('open'); const cypressConfigFilePath = require.resolve(`../../${argv.configFile}`) as string; const cypressConfigFile = await import(cypressConfigFilePath); - // KIBANA_MKI_USE_LATEST_COMMIT === 1 means that we are overriding the image for the periodic pipeline execution. - // We don't override the image when executing the tests on the second quality gate. - if ( - !process.env.KIBANA_MKI_USE_LATEST_COMMIT || - process.env.KIBANA_MKI_USE_LATEST_COMMIT !== '1' - ) { - cypressConfigFile.env.grepTags = - '@serverlessQA --@skipInServerless --@skipInServerlessMKI '; + + // if KIBANA_MKI_QUALITY_GATE exists and has a value, it means that we are running the tests against the second + // quality gate. + if (process.env.KIBANA_MKI_QUALITY_GATE) { + log.info( + 'KIBANA_MKI_QUALITY_GATE is provided, so @serverlessQA --@skipInServerless --@skipInServerlessMKI tags will run.' + ); + cypressConfigFile.env.grepTags = '@serverlessQA --@skipInServerless --@skipInServerlessMKI'; } + const tier: string = argv.tier; const endpointAddon: boolean = argv.endpointAddon; const cloudAddon: boolean = argv.cloudAddon; @@ -559,17 +411,10 @@ ${JSON.stringify(cypressConfigFile, null, 2)} ? getProductTypes(tier, endpointAddon, cloudAddon) : (parseTestFileConfig(filePath).productTypes as ProductType[]); - if (!API_KEY) { - log.info('API KEY to create project could not be retrieved.'); - // eslint-disable-next-line no-process-exit - return process.exit(1); - } - log.info(`${id}: Creating project ${PROJECT_NAME}...`); // Creating project for the test to run - const project = await createSecurityProject( + const project = await cloudHandler.createSecurityProject( PROJECT_NAME, - API_KEY, productTypes, commit ); @@ -581,12 +426,15 @@ ${JSON.stringify(cypressConfigFile, null, 2)} } context.addCleanupTask(() => { - const command = `curl -X DELETE ${BASE_ENV_URL}/api/v1/serverless/projects/security/${project.id} -H "Authorization: ApiKey ${API_KEY}"`; - exec(command); + let command: string; + if (cloudHandler instanceof CloudHandler) { + command = `curl -X DELETE ${BASE_ENV_URL}/api/v1/serverless/projects/security/${project.id} -H "Authorization: ApiKey ${API_KEY}"`; + exec(command); + } }); // Reset credentials for elastic user - const credentials = await resetCredentials(project.id, id, API_KEY); + const credentials = await cloudHandler.resetCredentials(project.id, id); if (!credentials) { log.info('Credentials could not be reset.'); @@ -595,7 +443,7 @@ ${JSON.stringify(cypressConfigFile, null, 2)} } // Wait for project to be initialized - await waitForProjectInitialized(project.id, API_KEY); + await cloudHandler.waitForProjectInitialized(project.id); // Base64 encode the credentials in order to invoke ES and KB APIs const auth = btoa(`${credentials.username}:${credentials.password}`); @@ -620,6 +468,9 @@ ${JSON.stringify(cypressConfigFile, null, 2)} ELASTICSEARCH_USERNAME: credentials.username, ELASTICSEARCH_PASSWORD: credentials.password, + // Used in order to handle the correct role_users file loading. + PROXY_ORG: PROXY_URL ? project.proxy_org_name : undefined, + KIBANA_URL: project.kb_url, KIBANA_USERNAME: credentials.username, KIBANA_PASSWORD: credentials.password, @@ -674,7 +525,7 @@ ${JSON.stringify(cypressConfigFile, null, 2)} } // Delete serverless project log.info(`${id} : Deleting project ${PROJECT_NAME}...`); - await deleteSecurityProject(project.id, PROJECT_NAME, API_KEY); + await cloudHandler.deleteSecurityProject(project.id, PROJECT_NAME); } catch (error) { // False positive // eslint-disable-next-line require-atomic-updates diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/cloud_project_handler.ts b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/cloud_project_handler.ts new file mode 100644 index 00000000000000..89166701ee2067 --- /dev/null +++ b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/cloud_project_handler.ts @@ -0,0 +1,170 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import axios, { AxiosError } from 'axios'; +import pRetry from 'p-retry'; +import type { + ProductType, + Project, + CreateProjectRequestBody, + Credentials, +} from './project_handler'; +import { ProjectHandler } from './project_handler'; + +const DEFAULT_REGION = 'aws-eu-west-1'; + +export class CloudHandler extends ProjectHandler { + apiKey: string; + + constructor(apiKey: string, baseEnvUrl: string) { + super(baseEnvUrl); + this.apiKey = apiKey; + } + + // Method to invoke the create project API for serverless. + async createSecurityProject( + projectName: string, + productTypes: ProductType[], + commit: string + ): Promise { + const body: CreateProjectRequestBody = { + name: projectName, + region_id: DEFAULT_REGION, + product_types: productTypes, + }; + + if (process.env.KIBANA_MKI_IMAGE_COMMIT || commit) { + const override = commit ? commit : process.env.KIBANA_MKI_IMAGE_COMMIT; + const kibanaOverrideImage = `${override?.substring(0, 12)}`; + this.log.info(`Kibana Image Commit under test: ${process.env.KIBANA_MKI_IMAGE_COMMIT}!`); + this.log.info( + `Overriding Kibana image in the MKI with docker.elastic.co/kibana-ci/kibana-serverless:git-${kibanaOverrideImage}` + ); + body.overrides = { + kibana: { + docker_image: `docker.elastic.co/kibana-ci/kibana-serverless:git-${kibanaOverrideImage}`, + }, + }; + } + + try { + const response = await axios.post( + `${this.baseEnvUrl}/api/v1/serverless/projects/security`, + body, + { + headers: { + Authorization: `ApiKey ${this.apiKey}`, + }, + } + ); + return { + name: response.data.name, + id: response.data.id, + region: response.data.region_id, + es_url: `${response.data.endpoints.elasticsearch}:443`, + kb_url: `${response.data.endpoints.kibana}:443`, + product: response.data.type, + }; + } catch (error) { + if (error instanceof AxiosError) { + const errorData = JSON.stringify(error.response?.data); + this.log.error(`${error.response?.status}:${errorData}`); + } else { + this.log.error(`${error.message}`); + } + } + } + + // Method to invoke the delete project API for serverless. + async deleteSecurityProject(projectId: string, projectName: string): Promise { + try { + await axios.delete(`${this.baseEnvUrl}/api/v1/serverless/projects/security/${projectId}`, { + headers: { + Authorization: `ApiKey ${this.apiKey}`, + }, + }); + this.log.info(`Project ${projectName} was successfully deleted!`); + } catch (error) { + if (error instanceof AxiosError) { + this.log.error(`${error.response?.status}:${error.response?.data}`); + } else { + this.log.error(`${error.message}`); + } + } + } + + // Method to reset the credentials for the created project. + resetCredentials(projectId: string, runnerId: string): Promise { + this.log.info(`${runnerId} : Reseting credentials`); + + const fetchResetCredentialsStatusAttempt = async (attemptNum: number) => { + const response = await axios.post( + `${this.baseEnvUrl}/api/v1/serverless/projects/security/${projectId}/_reset-internal-credentials`, + {}, + { + headers: { + Authorization: `ApiKey ${this.apiKey}`, + }, + } + ); + this.log.info('Credentials have ben reset'); + return { + password: response.data.password, + username: response.data.username, + }; + }; + + const retryOptions = { + onFailedAttempt: (error: Error | AxiosError) => { + if (error instanceof AxiosError && error.code === 'ENOTFOUND') { + this.log.info('Project is not reachable. A retry will be triggered soon..'); + } else { + this.log.error(`${error.message}`); + } + }, + retries: 100, + factor: 2, + maxTimeout: 20000, + }; + + return pRetry(fetchResetCredentialsStatusAttempt, retryOptions); + } + + // Wait until Project is initialized + waitForProjectInitialized(projectId: string): Promise { + const fetchProjectStatusAttempt = async (attemptNum: number) => { + this.log.info(`Retry number ${attemptNum} to check if project is initialized.`); + const response = await axios.get( + `${this.baseEnvUrl}/api/v1/serverless/projects/security/${projectId}/status`, + { + headers: { + Authorization: `ApiKey ${this.apiKey}`, + }, + } + ); + if (response.data.phase !== 'initialized') { + this.log.info(response.data); + throw new Error('Project is not initialized. A retry will be triggered soon...'); + } else { + this.log.info('Project is initialized'); + } + }; + const retryOptions = { + onFailedAttempt: (error: Error | AxiosError) => { + if (error instanceof AxiosError && error.code === 'ENOTFOUND') { + this.log.info('Project is not reachable. A retry will be triggered soon...'); + } else { + this.log.error(`${error.message}`); + } + }, + retries: 100, + factor: 2, + maxTimeout: 20000, + }; + return pRetry(fetchProjectStatusAttempt, retryOptions); + } +} diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/project_handler.ts b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/project_handler.ts new file mode 100644 index 00000000000000..199df9c4fb4c01 --- /dev/null +++ b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/project_handler.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ToolingLog } from '@kbn/tooling-log'; + +export interface ProductType { + product_line: string; + product_tier: string; +} + +export interface OverrideEntry { + docker_image: string; +} + +export interface ProductOverrides { + kibana?: OverrideEntry; + elasticsearch?: OverrideEntry; + fleet?: OverrideEntry; + cluster?: OverrideEntry; +} + +export interface CreateProjectRequestBody { + name: string; + region_id: string; + product_types?: ProductType[]; + overrides?: ProductOverrides; +} + +export interface Project { + name: string; + id: string; + region: string; + es_url: string; + kb_url: string; + product: string; + proxy_id?: number; + proxy_org_id?: number; + proxy_org_name?: string; +} + +export interface Credentials { + username: string; + password: string; +} + +export class ProjectHandler { + private readonly DEFAULT_ERROR_MSG: string = + 'The method needs to be overriden when the class is inherited!'; + + baseEnvUrl: string; + log: ToolingLog; + + constructor(baseEnvUrl: string) { + this.baseEnvUrl = baseEnvUrl; + this.log = new ToolingLog({ + level: 'info', + writeTo: process.stdout, + }); + } + + // Method to invoke the create project API for serverless. + async createSecurityProject( + projectName: string, + productTypes: ProductType[], + commit: string + ): Promise { + throw new Error(this.DEFAULT_ERROR_MSG); + } + + async deleteSecurityProject(projectId: string, projectName: string): Promise { + throw new Error(this.DEFAULT_ERROR_MSG); + } + + resetCredentials(projectId: string, runnerId: string): Promise { + throw new Error(this.DEFAULT_ERROR_MSG); + } + + waitForProjectInitialized(projectId: string): Promise { + throw new Error(this.DEFAULT_ERROR_MSG); + } +} diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/proxy_project_handler.ts b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/proxy_project_handler.ts new file mode 100644 index 00000000000000..08e5b418b83ac6 --- /dev/null +++ b/x-pack/plugins/security_solution/scripts/run_cypress/project_handler/proxy_project_handler.ts @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import axios, { AxiosError } from 'axios'; +import pRetry from 'p-retry'; +import type { + ProductType, + Project, + CreateProjectRequestBody, + Credentials, +} from './project_handler'; +import { ProjectHandler } from './project_handler'; + +const DEFAULT_REGION = 'aws-eu-west-1'; + +export class ProxyHandler extends ProjectHandler { + proxyAuth: string; + + constructor(baseEnvUrl: string, proxyClientId: string, proxySecret: string) { + super(baseEnvUrl); + this.proxyAuth = btoa(`${proxyClientId}:${proxySecret}`); + } + + // Method to invoke the create project API for serverless. + async createSecurityProject( + projectName: string, + productTypes: ProductType[], + commit: string + ): Promise { + const body: CreateProjectRequestBody = { + name: projectName, + region_id: DEFAULT_REGION, + product_types: productTypes, + }; + + if (process.env.KIBANA_MKI_IMAGE_COMMIT || commit) { + const override = commit ? commit : process.env.KIBANA_MKI_IMAGE_COMMIT; + const kibanaOverrideImage = `${override?.substring(0, 12)}`; + this.log.info(`Kibana Image Commit under test: ${process.env.KIBANA_MKI_IMAGE_COMMIT}!`); + this.log.info( + `Overriding Kibana image in the MKI with docker.elastic.co/kibana-ci/kibana-serverless:git-${kibanaOverrideImage}` + ); + body.overrides = { + kibana: { + docker_image: `docker.elastic.co/kibana-ci/kibana-serverless:git-${kibanaOverrideImage}`, + }, + }; + } + + try { + const response = await axios.post(`${this.baseEnvUrl}/projects`, body, { + headers: { + Authorization: `Basic ${this.proxyAuth}`, + }, + }); + return { + name: response.data.name, + id: response.data.project_id, + region: response.data.region_id, + es_url: `${response.data.elasticsearch_endpoint}:443`, + kb_url: `${response.data.kibana_endpoint}:443`, + product: response.data.project_type, + proxy_id: response.data.id, + proxy_org_id: response.data.organization_id, + proxy_org_name: response.data.organization_name, + }; + } catch (error) { + if (error instanceof AxiosError) { + const errorData = JSON.stringify(error.response?.data); + this.log.error(`${error.response?.status}:${errorData}`); + } else { + this.log.error(`${error.message}`); + } + } + } + + // Method to invoke the delete project API for serverless. + async deleteSecurityProject(projectId: string, projectName: string): Promise { + try { + await axios.delete(`${this.baseEnvUrl}/projects/${projectId}`, { + headers: { + Authorization: `Basic ${this.proxyAuth}`, + }, + }); + this.log.info(`Project ${projectName} was successfully deleted!`); + } catch (error) { + if (error instanceof AxiosError) { + this.log.error(`${error.response?.status}:${error.response?.data}`); + } else { + this.log.error(`${error.message}`); + } + } + } + + // Method to reset the credentials for the created project. + resetCredentials(projectId: string, runnerId: string): Promise { + this.log.info(`${runnerId} : Reseting credentials`); + + const fetchResetCredentialsStatusAttempt = async (attemptNum: number) => { + const response = await axios.post( + `${this.baseEnvUrl}/projects/${projectId}/_reset-internal-credentials`, + {}, + { + headers: { + Authorization: `Basic ${this.proxyAuth}`, + }, + } + ); + this.log.info('Credentials have ben reset'); + return { + password: response.data.password, + username: response.data.username, + }; + }; + + const retryOptions = { + onFailedAttempt: (error: Error | AxiosError) => { + if (error instanceof AxiosError && error.code === 'ENOTFOUND') { + this.log.info('Project is not reachable. A retry will be triggered soon..'); + } else { + this.log.error(`${error.message}`); + } + }, + retries: 100, + factor: 2, + maxTimeout: 20000, + }; + + return pRetry(fetchResetCredentialsStatusAttempt, retryOptions); + } + + // Wait until Project is initialized + waitForProjectInitialized(projectId: string): Promise { + const fetchProjectStatusAttempt = async (attemptNum: number) => { + this.log.info(`Retry number ${attemptNum} to check if project is initialized.`); + const response = await axios.get(`${this.baseEnvUrl}/projects/${projectId}/status`, { + headers: { + Authorization: `Basic ${this.proxyAuth}`, + }, + }); + if (response.data.phase !== 'initialized') { + this.log.info(response.data); + throw new Error('Project is not initialized. A retry will be triggered soon...'); + } else { + this.log.info('Project is initialized'); + } + }; + const retryOptions = { + onFailedAttempt: (error: Error | AxiosError) => { + if (error instanceof AxiosError && error.code === 'ENOTFOUND') { + this.log.info('Project is not reachable. A retry will be triggered soon...'); + } else { + this.log.error(`${error.message}`); + } + }, + retries: 100, + factor: 2, + maxTimeout: 20000, + }; + return pRetry(fetchProjectStatusAttempt, retryOptions); + } +} diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/util.test.ts b/x-pack/plugins/security_solution_serverless/public/navigation/util.test.ts index fd03f6f2ecdf30..6ea4a935b99987 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/util.test.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/util.test.ts @@ -12,7 +12,7 @@ const cloud = { serverless: { projectId: '1234', }, - projectsUrl: 'https://cloud.elastic.co/projects', + projectsUrl: 'https://cloud.elastic.co/projects/', } as CloudStart; describe('util', () => { @@ -29,7 +29,7 @@ describe('util', () => { it('should return the correct url', () => { expect(getProjectFeaturesUrl(cloud)).toBe( - `${cloud.projectsUrl}/security/${cloud.serverless?.projectId}?open=securityProjectFeatures` + `${cloud.projectsUrl}security/${cloud.serverless?.projectId}?open=securityProjectFeatures` ); }); }); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/util.ts b/x-pack/plugins/security_solution_serverless/public/navigation/util.ts index d57b4f7e1a4abe..ca7e1e07d6922b 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/util.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/util.ts @@ -20,7 +20,7 @@ export const getProjectFeaturesUrl = (cloud: CloudStart): string | undefined => if (!projectsBaseUrl || !projectId) { return undefined; } - return `${projectsBaseUrl}/${SECURITY_PROJECT_TYPE}/${projectId}?open=securityProjectFeatures`; + return `${projectsBaseUrl}${SECURITY_PROJECT_TYPE}/${projectId}?open=securityProjectFeatures`; }; export const getCloudUrl: GetCloudUrl = (cloudUrlKey, cloud) => { diff --git a/x-pack/plugins/serverless_search/public/application/connectors.tsx b/x-pack/plugins/serverless_search/public/application/connectors.tsx index 44d125117d2a84..0a00f6a5d68587 100644 --- a/x-pack/plugins/serverless_search/public/application/connectors.tsx +++ b/x-pack/plugins/serverless_search/public/application/connectors.tsx @@ -9,7 +9,7 @@ import { CoreStart } from '@kbn/core-lifecycle-browser'; import { I18nProvider } from '@kbn/i18n-react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -28,7 +28,7 @@ export async function renderApp( const { ConnectorsRouter } = await import('./components/connectors_router'); ReactDOM.render( - + @@ -39,7 +39,7 @@ export async function renderApp( - , + , element ); return () => ReactDOM.unmountComponentAtNode(element); diff --git a/x-pack/plugins/serverless_search/public/application/elasticsearch.tsx b/x-pack/plugins/serverless_search/public/application/elasticsearch.tsx index 5413ff6361cf9c..8693ce18ace9d6 100644 --- a/x-pack/plugins/serverless_search/public/application/elasticsearch.tsx +++ b/x-pack/plugins/serverless_search/public/application/elasticsearch.tsx @@ -9,10 +9,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { CoreStart } from '@kbn/core/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { I18nProvider } from '@kbn/i18n-react'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; -import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { Route, Router, Routes } from '@kbn/shared-ux-router'; import { ServerlessSearchContext } from './hooks/use_kibana'; @@ -24,22 +23,20 @@ export async function renderApp( ) { const { ElasticsearchOverview } = await import('./components/overview'); ReactDOM.render( - + - - - - - - - - - + + + + + + + - , + , element ); return () => ReactDOM.unmountComponentAtNode(element); diff --git a/x-pack/plugins/serverless_search/public/test/test_utils.tsx b/x-pack/plugins/serverless_search/public/test/test_utils.tsx index ffc81b0b6daf6a..1012528cd3b360 100644 --- a/x-pack/plugins/serverless_search/public/test/test_utils.tsx +++ b/x-pack/plugins/serverless_search/public/test/test_utils.tsx @@ -8,7 +8,8 @@ import React, { ReactElement } from 'react'; import { render, RenderOptions } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { I18nProvider } from '@kbn/i18n-react'; import { coreMock } from '@kbn/core/public/mocks'; @@ -33,7 +34,7 @@ const queryClient = new QueryClient({ const AllTheProviders: React.FC = ({ children }) => { return ( - + {children} diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json index d8762b4156ac2c..aa444365942733 100644 --- a/x-pack/plugins/serverless_search/tsconfig.json +++ b/x-pack/plugins/serverless_search/tsconfig.json @@ -47,5 +47,6 @@ "@kbn/discover-plugin", "@kbn/search-connectors-plugin", "@kbn/index-management", + "@kbn/react-kibana-context-render", ] } diff --git a/x-pack/plugins/transform/public/app/app.tsx b/x-pack/plugins/transform/public/app/app.tsx index 9da8af2ba84074..c6481f74404259 100644 --- a/x-pack/plugins/transform/public/app/app.tsx +++ b/x-pack/plugins/transform/public/app/app.tsx @@ -9,11 +9,10 @@ import React, { type FC } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { EuiErrorBoundary } from '@elastic/eui'; - import { Router, Routes, Route } from '@kbn/shared-ux-router'; import type { ScopedHistory } from '@kbn/core/public'; -import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import type { ExperimentalFeatures } from '../../common/config'; import { SECTION_SLUG } from './common/constants'; @@ -49,8 +48,6 @@ export const renderApp = ( enabledFeatures: TransformEnabledFeatures, experimentalFeatures: ExperimentalFeatures ) => { - const I18nContext = appDependencies.i18n.Context; - const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -61,21 +58,17 @@ export const renderApp = ( }); render( - + - - - - - - - - - - - + + + + + + + - , + , element ); diff --git a/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx b/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx index 8b2f154e8124e9..aecca492671779 100644 --- a/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx +++ b/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx @@ -38,7 +38,7 @@ export const ToastNotificationText: FC = ({ inline = false, forceModal = false, }) => { - const { overlays, theme, i18n: i18nStart } = useAppDependencies(); + const { overlays, ...startServices } = useAppDependencies(); if (!forceModal && typeof text === 'string' && text.length <= MAX_SIMPLE_MESSAGE_LENGTH) { return text; @@ -87,7 +87,7 @@ export const ToastNotificationText: FC = ({ , - { theme, i18n: i18nStart } + startServices ) ); }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_create_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_create_transform.tsx index c678549683becb..53682db36c0291 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_create_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_create_transform.tsx @@ -32,7 +32,7 @@ interface CreateTransformArgs { } export const useCreateTransform = () => { - const { http, i18n: i18nStart, theme } = useAppDependencies(); + const { http, ...startServices } = useAppDependencies(); const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); @@ -42,10 +42,7 @@ export const useCreateTransform = () => { defaultMessage: 'An error occurred creating the transform {transformId}:', values: { transformId }, }), - text: toMountPoint(, { - theme, - i18n: i18nStart, - }), + text: toMountPoint(, startServices), }); } diff --git a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx index 9f2cf91b088b1d..c2549b0edad56d 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx @@ -87,7 +87,7 @@ export const useDeleteIndexAndTargetIndex = (items: TransformListRow[]) => { }; export const useDeleteTransforms = () => { - const { http, i18n: i18nStart, theme } = useAppDependencies(); + const { http, ...startServices } = useAppDependencies(); const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); @@ -104,7 +104,7 @@ export const useDeleteTransforms = () => { }), text: toMountPoint( , - { theme, i18n: i18nStart } + startServices ), }), onSuccess: (results) => { @@ -121,10 +121,10 @@ export const useDeleteTransforms = () => { defaultMessage: 'An error occurred deleting the transform {transformId}', values: { transformId }, }), - text: toMountPoint(, { - theme, - i18n: i18nStart, - }), + text: toMountPoint( + , + startServices + ), }); } @@ -138,10 +138,10 @@ export const useDeleteTransforms = () => { values: { destinationIndex }, } ), - text: toMountPoint(, { - theme, - i18n: i18nStart, - }), + text: toMountPoint( + , + startServices + ), }); } @@ -155,10 +155,10 @@ export const useDeleteTransforms = () => { values: { destinationIndex }, } ), - text: toMountPoint(, { - theme, - i18n: i18nStart, - }), + text: toMountPoint( + , + startServices + ), }); } } diff --git a/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx index 9ecd1b8717243b..0465a178c09a3b 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_reauthorize_transform.tsx @@ -25,7 +25,7 @@ import { ToastNotificationText } from '../components'; import { useRefreshTransformList } from './use_refresh_transform_list'; export const useReauthorizeTransforms = () => { - const { http, i18n: i18nStart, theme } = useAppDependencies(); + const { http, ...startServices } = useAppDependencies(); const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); @@ -46,10 +46,7 @@ export const useReauthorizeTransforms = () => { defaultMessage: 'An error occurred calling the reauthorize transforms request.', } ), - text: toMountPoint(, { - theme, - i18n: i18nStart, - }), + text: toMountPoint(, startServices), }), onSuccess: (results) => { for (const transformId in results) { diff --git a/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx index 1f415eae1ad203..fafc26581fa09e 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_reset_transform.tsx @@ -24,7 +24,7 @@ import { ToastNotificationText } from '../components'; import { useRefreshTransformList } from './use_refresh_transform_list'; export const useResetTransforms = () => { - const { http, i18n: i18nStart, theme } = useAppDependencies(); + const { http, ...startServices } = useAppDependencies(); const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); @@ -41,10 +41,7 @@ export const useResetTransforms = () => { }), text: toMountPoint( , - { - theme, - i18n: i18nStart, - } + startServices ), }), onSuccess: (results) => { @@ -60,10 +57,10 @@ export const useResetTransforms = () => { defaultMessage: 'An error occurred resetting the transform {transformId}', values: { transformId }, }), - text: toMountPoint(, { - theme, - i18n: i18nStart, - }), + text: toMountPoint( + , + startServices + ), }); } } diff --git a/x-pack/plugins/transform/public/app/hooks/use_schedule_now_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_schedule_now_transform.tsx index bb568673a57587..22201b28068483 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_schedule_now_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_schedule_now_transform.tsx @@ -24,7 +24,7 @@ import { ToastNotificationText } from '../components'; import { useRefreshTransformList } from './use_refresh_transform_list'; export const useScheduleNowTransforms = () => { - const { http, i18n: i18nStart, theme } = useAppDependencies(); + const { http, ...startServices } = useAppDependencies(); const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); @@ -46,10 +46,7 @@ export const useScheduleNowTransforms = () => { 'An error occurred calling the request to schedule the transform to process data instantly.', } ), - text: toMountPoint(, { - theme, - i18n: i18nStart, - }), + text: toMountPoint(, startServices), }), onSuccess: (results) => { for (const transformId in results) { diff --git a/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx index 104c3145fc2591..2175ccffeea536 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx @@ -24,7 +24,7 @@ import { ToastNotificationText } from '../components'; import { useRefreshTransformList } from './use_refresh_transform_list'; export const useStartTransforms = () => { - const { http, i18n: i18nStart, theme } = useAppDependencies(); + const { http, ...startServices } = useAppDependencies(); const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); @@ -42,10 +42,7 @@ export const useStartTransforms = () => { defaultMessage: 'An error occurred calling the start transforms request.', } ), - text: toMountPoint(, { - theme, - i18n: i18nStart, - }), + text: toMountPoint(, startServices), }), onSuccess: (results) => { for (const transformId in results) { diff --git a/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx index 564b17feac3f98..439c654ded1691 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx @@ -25,7 +25,7 @@ import { ToastNotificationText } from '../components'; import { useRefreshTransformList } from './use_refresh_transform_list'; export const useStopTransforms = () => { - const { http, i18n: i18nStart, theme } = useAppDependencies(); + const { http, ...startServices } = useAppDependencies(); const refreshTransformList = useRefreshTransformList(); const toastNotifications = useToastNotifications(); @@ -43,10 +43,7 @@ export const useStopTransforms = () => { defaultMessage: 'An error occurred called the stop transforms request.', } ), - text: toMountPoint(, { - theme, - i18n: i18nStart, - }), + text: toMountPoint(, startServices), }), onSuccess: (results) => { for (const transformId in results) { diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx index 35e22d07fe793a..e15dec45908a4b 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx @@ -84,7 +84,7 @@ export const StepCreateForm: FC = React.memo( const [discoverLink, setDiscoverLink] = useState(); const toastNotifications = useToastNotifications(); - const { application, i18n: i18nStart, share, theme } = useAppDependencies(); + const { application, share, ...startServices } = useAppDependencies(); const isDiscoverAvailable = application.capabilities.discover?.show ?? false; useEffect(() => { @@ -200,13 +200,13 @@ export const StepCreateForm: FC = React.memo( title: i18n.translate('xpack.transform.stepCreateForm.progressErrorMessage', { defaultMessage: 'An error occurred getting the progress percentage:', }), - text: toMountPoint(, { - theme, - i18n: i18nStart, - }), + text: toMountPoint( + , + startServices + ), }); } - }, [i18nStart, stats, theme, toastNotifications, transformConfig, transformId]); + }, [stats, toastNotifications, transformConfig, transformId, startServices]); function getTransformConfigDevConsoleStatement() { return `PUT _transform/${transformId}\n${JSON.stringify(transformConfig, null, 2)}\n\n`; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index 9f60fe827ae600..416feac4c3c336 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -76,7 +76,7 @@ interface StepDetailsFormProps { export const StepDetailsForm: FC = React.memo( ({ overrides = {}, onChange, searchItems, stepDefineState }) => { - const { application, i18n: i18nStart, theme } = useAppDependencies(); + const { application, ...startServices } = useAppDependencies(); const { capabilities } = application; const toastNotifications = useToastNotifications(); const { esIndicesCreateIndex } = useDocumentationLinks(); @@ -167,10 +167,10 @@ export const StepDetailsForm: FC = React.memo( title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformList', { defaultMessage: 'An error occurred getting the existing transform IDs:', }), - text: toMountPoint(, { - theme, - i18n: i18nStart, - }), + text: toMountPoint( + , + startServices + ), }); } // custom comparison @@ -185,7 +185,7 @@ export const StepDetailsForm: FC = React.memo( }), text: toMountPoint( , - { theme, i18n: i18nStart } + startServices ), }); } @@ -202,10 +202,10 @@ export const StepDetailsForm: FC = React.memo( title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIndexNames', { defaultMessage: 'An error occurred getting the existing index names:', }), - text: toMountPoint(, { - theme, - i18n: i18nStart, - }), + text: toMountPoint( + , + startServices + ), }); } // custom comparison @@ -224,7 +224,7 @@ export const StepDetailsForm: FC = React.memo( }), text: toMountPoint( , - { theme, i18n: i18nStart } + startServices ), }); } @@ -242,7 +242,7 @@ export const StepDetailsForm: FC = React.memo( }), text: toMountPoint( , - { theme, i18n: i18nStart } + startServices ), }); } diff --git a/x-pack/plugins/transform/public/app/sections/edit_transform/components/edit_transform_retention_policy.tsx b/x-pack/plugins/transform/public/app/sections/edit_transform/components/edit_transform_retention_policy.tsx index c32409cb6ff7f9..b49eb67b0db08c 100644 --- a/x-pack/plugins/transform/public/app/sections/edit_transform/components/edit_transform_retention_policy.tsx +++ b/x-pack/plugins/transform/public/app/sections/edit_transform/components/edit_transform_retention_policy.tsx @@ -30,7 +30,7 @@ import { useRetentionPolicyField } from '../state_management/selectors/retention import { EditTransformFlyoutFormTextInput } from './edit_transform_flyout_form_text_input'; export const EditTransformRetentionPolicy: FC = () => { - const { i18n: i18nStart, theme } = useAppDependencies(); + const startServices = useAppDependencies(); const toastNotifications = useToastNotifications(); @@ -70,7 +70,7 @@ export const EditTransformRetentionPolicy: FC = () => { }), text: toMountPoint( , - { theme, i18n: i18nStart } + startServices ), }); } diff --git a/x-pack/plugins/transform/tsconfig.json b/x-pack/plugins/transform/tsconfig.json index faa4e2e67dd53b..91581e7dfc4ffd 100644 --- a/x-pack/plugins/transform/tsconfig.json +++ b/x-pack/plugins/transform/tsconfig.json @@ -74,7 +74,8 @@ "@kbn/ml-creation-wizard-utils", "@kbn/alerts-as-data-utils", "@kbn/code-editor", - "@kbn/rule-data-utils" + "@kbn/rule-data-utils", + "@kbn/react-kibana-context-render" ], "exclude": [ "target/**/*", diff --git a/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts b/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts index 0026f6c91ec27f..4c26b46a0f62a7 100644 --- a/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts +++ b/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts @@ -30,11 +30,16 @@ export const samlAuthentication = async ( on('task', { getSessionCookie: async (role: string | SecurityRoleName): Promise => { - const sessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud: config.env.CLOUD_SERVERLESS, - }); + // If config.env.PROXY_ORG is set, it means that proxy service is used to create projects. Define the proxy org filename to override the roles. + const rolesFilename = config.env.PROXY_ORG ? `${config.env.PROXY_ORG}.json` : undefined; + const sessionManager = new SamlSessionManager( + { + hostOptions, + log, + isCloud: config.env.CLOUD_SERVERLESS, + }, + rolesFilename + ); return sessionManager.getSessionCookieForRole(role); }, });