diff --git a/.ci/Dockerfile b/.ci/Dockerfile index cf827fc0ed08fc..8a972c65f84125 100644 --- a/.ci/Dockerfile +++ b/.ci/Dockerfile @@ -1,7 +1,7 @@ # NOTE: This Dockerfile is ONLY used to run certain tasks in CI. It is not used to run Kibana or as a distributable. # If you're looking for the Kibana Docker image distributable, please see: src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts -ARG NODE_VERSION=14.15.2 +ARG NODE_VERSION=14.15.3 FROM node:${NODE_VERSION} AS base diff --git a/.ci/teamcity/bootstrap.sh b/.ci/teamcity/bootstrap.sh index adb884ca064ba5..fc57811bb20771 100755 --- a/.ci/teamcity/bootstrap.sh +++ b/.ci/teamcity/bootstrap.sh @@ -7,7 +7,7 @@ source "$(dirname "${0}")/util.sh" tc_start_block "Bootstrap" tc_start_block "yarn install and kbn bootstrap" -verify_no_git_changes yarn kbn bootstrap --prefer-offline +verify_no_git_changes yarn kbn bootstrap tc_end_block "yarn install and kbn bootstrap" tc_start_block "build kbn-pm" diff --git a/.ci/teamcity/checks/bundle_limits.sh b/.ci/teamcity/checks/bundle_limits.sh index 3f7daef6d04731..751ec5a03ee7bc 100755 --- a/.ci/teamcity/checks/bundle_limits.sh +++ b/.ci/teamcity/checks/bundle_limits.sh @@ -4,4 +4,5 @@ set -euo pipefail source "$(dirname "${0}")/../util.sh" -node scripts/build_kibana_platform_plugins --validate-limits +checks-reporter-with-killswitch "Check Bundle Limits" \ + node scripts/build_kibana_platform_plugins --validate-limits diff --git a/.ci/teamcity/checks/commit.sh b/.ci/teamcity/checks/commit.sh new file mode 100755 index 00000000000000..387ec0c1267858 --- /dev/null +++ b/.ci/teamcity/checks/commit.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +# Runs pre-commit hook script for the files touched in the last commit. +# That way we can ensure a set of quick commit checks earlier as we removed +# the pre-commit hook installation by default. +# If files are more than 200 we will skip it and just use +# the further ci steps that already check linting and file casing for the entire repo. +checks-reporter-with-killswitch "Quick commit checks" \ + "$(dirname "${0}")/commit_check_runner.sh" diff --git a/.ci/teamcity/checks/commit_check_runner.sh b/.ci/teamcity/checks/commit_check_runner.sh new file mode 100755 index 00000000000000..f2a4a205682154 --- /dev/null +++ b/.ci/teamcity/checks/commit_check_runner.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +echo "!!!!!!!! ATTENTION !!!!!!!! +That check is intended to provide earlier CI feedback after we remove the automatic install for the local pre-commit hook. +If you want, you can still manually install the pre-commit hook locally by running 'node scripts/register_git_hook locally' +!!!!!!!!!!!!!!!!!!!!!!!!!!! +" + +node scripts/precommit_hook.js --ref HEAD~1..HEAD --max-files 200 --verbose diff --git a/.ci/teamcity/checks/jest_configs.sh b/.ci/teamcity/checks/jest_configs.sh new file mode 100755 index 00000000000000..6703ffffb56515 --- /dev/null +++ b/.ci/teamcity/checks/jest_configs.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +checks-reporter-with-killswitch "Check Jest Configs" \ + node scripts/check_jest_configs diff --git a/.ci/teamcity/checks/plugins_with_circular_deps.sh b/.ci/teamcity/checks/plugins_with_circular_deps.sh new file mode 100755 index 00000000000000..5acc4b2ae351b0 --- /dev/null +++ b/.ci/teamcity/checks/plugins_with_circular_deps.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +checks-reporter-with-killswitch "Check Plugins With Circular Dependencies" \ + node scripts/find_plugins_with_circular_deps diff --git a/.ci/teamcity/oss/plugin_functional.sh b/.ci/teamcity/oss/plugin_functional.sh index 5d1ecbcbd48ee4..3570bf01e49c49 100755 --- a/.ci/teamcity/oss/plugin_functional.sh +++ b/.ci/teamcity/oss/plugin_functional.sh @@ -13,6 +13,21 @@ if [[ ! -d "target" ]]; then fi cd - -./test/scripts/test/plugin_functional.sh -./test/scripts/test/example_functional.sh -./test/scripts/test/interpreter_functional.sh +checks-reporter-with-killswitch "Plugin Functional Tests" \ + node scripts/functional_tests \ + --config test/plugin_functional/config.ts \ + --bail \ + --debug + +checks-reporter-with-killswitch "Example Functional Tests" \ + node scripts/functional_tests \ + --config test/examples/config.js \ + --bail \ + --debug + +checks-reporter-with-killswitch "Interpreter Functional Tests" \ + node scripts/functional_tests \ + --config test/interpreter_functional/config.ts \ + --bail \ + --debug \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" diff --git a/.ci/teamcity/setup_env.sh b/.ci/teamcity/setup_env.sh index f662d36247a2fd..982d129dae2a6d 100755 --- a/.ci/teamcity/setup_env.sh +++ b/.ci/teamcity/setup_env.sh @@ -25,12 +25,14 @@ tc_set_env FORCE_COLOR 1 tc_set_env TEST_BROWSER_HEADLESS 1 tc_set_env ELASTIC_APM_ENVIRONMENT ci +tc_set_env ELASTIC_APM_TRANSACTION_SAMPLE_RATE 0.1 if [[ "${KIBANA_CI_REPORTER_KEY_BASE64-}" ]]; then tc_set_env KIBANA_CI_REPORTER_KEY "$(echo "$KIBANA_CI_REPORTER_KEY_BASE64" | base64 -d)" fi if is_pr; then + tc_set_env ELASTIC_APM_ACTIVE false tc_set_env CHECKS_REPORTER_ACTIVE true # These can be removed once we're not supporting Jenkins and TeamCity at the same time @@ -39,6 +41,7 @@ if is_pr; then tc_set_env ghprbActualCommit "$GITHUB_PR_TRIGGERED_SHA" tc_set_env BUILD_URL "$TEAMCITY_BUILD_URL" else + tc_set_env ELASTIC_APM_ACTIVE true tc_set_env CHECKS_REPORTER_ACTIVE false fi diff --git a/.node-version b/.node-version index 420568d75691b9..19c4c189d3640c 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -14.15.2 +14.15.3 diff --git a/.nvmrc b/.nvmrc index 420568d75691b9..19c4c189d3640c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -14.15.2 +14.15.3 diff --git a/.teamcity/pom.xml b/.teamcity/pom.xml index 5fa068d0a92e03..e6ec1f1c043c25 100644 --- a/.teamcity/pom.xml +++ b/.teamcity/pom.xml @@ -46,6 +46,14 @@ true + + teamcity + https://artifactory.elstc.co/artifactory/teamcity + + true + always + + @@ -53,6 +61,10 @@ JetBrains https://download.jetbrains.com/teamcity-repository + + teamcity + https://artifactory.elstc.co/artifactory/teamcity + @@ -124,5 +136,10 @@ junit 4.13 + + co.elastic.teamcity + teamcity-common + 1.0.0-SNAPSHOT + diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts index ec1b1c6eb94ef6..28108d019327b6 100644 --- a/.teamcity/settings.kts +++ b/.teamcity/settings.kts @@ -2,7 +2,7 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.* import projects.Kibana import projects.KibanaConfiguration -version = "2020.1" +version = "2020.2" val config = KibanaConfiguration { agentNetwork = DslContext.getParameter("agentNetwork", "teamcity") diff --git a/.teamcity/src/Agents.kt b/.teamcity/src/Agents.kt new file mode 100644 index 00000000000000..557cce80d0f551 --- /dev/null +++ b/.teamcity/src/Agents.kt @@ -0,0 +1,28 @@ +import co.elastic.teamcity.common.GoogleCloudAgent +import co.elastic.teamcity.common.GoogleCloudAgentDiskType +import co.elastic.teamcity.common.GoogleCloudProfile + +private val sizes = listOf("2", "4", "8", "16") + +val StandardAgents = sizes.map { size -> size to GoogleCloudAgent { + sourceImageFamily = "elastic-kibana-ci-ubuntu-1804-lts" + agentPrefix = "kibana-standard-$size-" + machineType = "n2-standard-$size" + diskSizeGb = 75 + diskType = GoogleCloudAgentDiskType.SSD +} }.toMap() + +val BuildAgent = GoogleCloudAgent { + sourceImageFamily = "elastic-kibana-ci-ubuntu-1804-lts" + agentPrefix = "kibana-c2-16-" + machineType = "c2-standard-16" + diskSizeGb = 250 + diskType = GoogleCloudAgentDiskType.SSD +} + +val CloudProfile = GoogleCloudProfile { + accessKeyId = "447fdd4d-7129-46b7-9822-2e57658c7422" + + agents(StandardAgents) + agent(BuildAgent) +} diff --git a/.teamcity/src/Extensions.kt b/.teamcity/src/Extensions.kt index 120b333d43e724..2942a6385f13f4 100644 --- a/.teamcity/src/Extensions.kt +++ b/.teamcity/src/Extensions.kt @@ -1,9 +1,7 @@ +import co.elastic.teamcity.common.requireAgent import jetbrains.buildServer.configs.kotlin.v2019_2.* -import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.notifications import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.ScriptBuildStep import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script -import jetbrains.buildServer.configs.kotlin.v2019_2.ui.insert -import projects.kibanaConfiguration fun BuildFeatures.junit(dirs: String = "target/**/TEST-*.xml") { feature { @@ -13,40 +11,8 @@ fun BuildFeatures.junit(dirs: String = "target/**/TEST-*.xml") { } } -fun ProjectFeatures.kibanaAgent(init: ProjectFeature.() -> Unit) { - feature { - type = "CloudImage" - param("network", kibanaConfiguration.agentNetwork) - param("subnet", kibanaConfiguration.agentSubnet) - param("growingId", "true") - param("agent_pool_id", "-2") - param("preemptible", "false") - param("sourceProject", "elastic-images-prod") - param("sourceImageFamily", "elastic-kibana-ci-ubuntu-1804-lts") - param("zone", "us-central1-a") - param("profileId", "kibana") - param("diskType", "pd-ssd") - param("machineCustom", "false") - param("maxInstances", "200") - param("imageType", "ImageFamily") - param("diskSizeGb", "75") // TODO - init() - } -} - -fun ProjectFeatures.kibanaAgent(size: String, init: ProjectFeature.() -> Unit = {}) { - kibanaAgent { - id = "KIBANA_STANDARD_$size" - param("source-id", "kibana-standard-$size-") - param("machineType", "n2-standard-$size") - init() - } -} - fun BuildType.kibanaAgent(size: String) { - requirements { - startsWith("teamcity.agent.name", "kibana-standard-$size-", "RQ_AGENT_NAME") - } + requireAgent(StandardAgents[size]!!) } fun BuildType.kibanaAgent(size: Int) { diff --git a/.teamcity/src/builds/Checks.kt b/.teamcity/src/builds/Checks.kt index 1228ea4d94f4c9..37336316c4c914 100644 --- a/.teamcity/src/builds/Checks.kt +++ b/.teamcity/src/builds/Checks.kt @@ -11,16 +11,18 @@ object Checks : BuildType({ kibanaAgent(4) val checkScripts = mapOf( + "Quick Commit Checks" to ".ci/teamcity/checks/commit.sh", "Check Telemetry Schema" to ".ci/teamcity/checks/telemetry.sh", "Check TypeScript Projects" to ".ci/teamcity/checks/ts_projects.sh", "Check File Casing" to ".ci/teamcity/checks/file_casing.sh", "Check Licenses" to ".ci/teamcity/checks/licenses.sh", "Verify NOTICE" to ".ci/teamcity/checks/verify_notice.sh", - "Test Hardening" to ".ci/teamcity/checks/test_hardening.sh", "Check Types" to ".ci/teamcity/checks/type_check.sh", + "Check Jest Configs" to ".ci/teamcity/checks/jest_configs.sh", "Check Doc API Changes" to ".ci/teamcity/checks/doc_api_changes.sh", "Check Bundle Limits" to ".ci/teamcity/checks/bundle_limits.sh", - "Check i18n" to ".ci/teamcity/checks/i18n.sh" + "Check i18n" to ".ci/teamcity/checks/i18n.sh", + "Check Plugins With Circular Dependencies" to ".ci/teamcity/checks/plugins_with_circular_deps.sh" ) steps { diff --git a/.teamcity/src/builds/default/DefaultCiGroup.kt b/.teamcity/src/builds/default/DefaultCiGroup.kt index 7dbe9cd0ba84c4..2c3b0d348591e3 100755 --- a/.teamcity/src/builds/default/DefaultCiGroup.kt +++ b/.teamcity/src/builds/default/DefaultCiGroup.kt @@ -1,5 +1,7 @@ package builds.default +import StandardAgents +import co.elastic.teamcity.common.requireAgent import jetbrains.buildServer.configs.kotlin.v2019_2.* import runbld @@ -11,5 +13,7 @@ class DefaultCiGroup(val ciGroup: Int = 0, init: BuildType.() -> Unit = {}) : De runbld("Default CI Group $ciGroup", "./.ci/teamcity/default/ci_group.sh $ciGroup") } + requireAgent(StandardAgents["4"]!!) + init() }) diff --git a/.teamcity/src/builds/default/DefaultCiGroups.kt b/.teamcity/src/builds/default/DefaultCiGroups.kt index 6f1d45598c92eb..4f39283149e73b 100644 --- a/.teamcity/src/builds/default/DefaultCiGroups.kt +++ b/.teamcity/src/builds/default/DefaultCiGroups.kt @@ -3,7 +3,7 @@ package builds.default import dependsOn import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -const val DEFAULT_CI_GROUP_COUNT = 10 +const val DEFAULT_CI_GROUP_COUNT = 11 val defaultCiGroups = (1..DEFAULT_CI_GROUP_COUNT).map { DefaultCiGroup(it) } object DefaultCiGroups : BuildType({ diff --git a/.teamcity/src/builds/es_snapshots/Verify.kt b/.teamcity/src/builds/es_snapshots/Verify.kt index c778814af536c4..4c0307e9eca550 100644 --- a/.teamcity/src/builds/es_snapshots/Verify.kt +++ b/.teamcity/src/builds/es_snapshots/Verify.kt @@ -6,7 +6,7 @@ import builds.default.defaultCiGroups import builds.oss.OssBuild import builds.oss.OssPluginFunctional import builds.oss.ossCiGroups -import builds.test.ApiServerIntegration +import builds.oss.OssApiServerIntegration import builds.test.JestIntegration import dependsOn import jetbrains.buildServer.configs.kotlin.v2019_2.* @@ -49,7 +49,7 @@ val defaultBuildsToClone = listOf( val defaultCloned = defaultBuildsToClone.map { cloneForVerify(it) } val integrationsBuildsToClone = listOf( - ApiServerIntegration, + OssApiServerIntegration, JestIntegration ) diff --git a/.teamcity/src/builds/test/ApiServerIntegration.kt b/.teamcity/src/builds/oss/OssApiServerIntegration.kt similarity index 62% rename from .teamcity/src/builds/test/ApiServerIntegration.kt rename to .teamcity/src/builds/oss/OssApiServerIntegration.kt index ca58b628cbd221..a04512fb2aba52 100644 --- a/.teamcity/src/builds/test/ApiServerIntegration.kt +++ b/.teamcity/src/builds/oss/OssApiServerIntegration.kt @@ -1,10 +1,8 @@ -package builds.test +package builds.oss -import addTestSettings -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType import runbld -object ApiServerIntegration : BuildType({ +object OssApiServerIntegration : OssFunctionalBase({ name = "API/Server Integration" description = "Executes API and Server Integration Tests" @@ -12,6 +10,4 @@ object ApiServerIntegration : BuildType({ runbld("API Integration", "./.ci/teamcity/oss/api_integration.sh") runbld("Server Integration", "./.ci/teamcity/oss/server_integration.sh") } - - addTestSettings() }) diff --git a/.teamcity/src/builds/test/AllTests.kt b/.teamcity/src/builds/test/AllTests.kt index d1b5898d1a5f5e..9506d98cbe50ee 100644 --- a/.teamcity/src/builds/test/AllTests.kt +++ b/.teamcity/src/builds/test/AllTests.kt @@ -1,5 +1,6 @@ package builds.test +import builds.oss.OssApiServerIntegration import dependsOn import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType @@ -8,5 +9,5 @@ object AllTests : BuildType({ description = "All Non-Functional Tests" type = Type.COMPOSITE - dependsOn(QuickTests, Jest, XPackJest, JestIntegration, ApiServerIntegration) + dependsOn(QuickTests, Jest, XPackJest, JestIntegration, OssApiServerIntegration) }) diff --git a/.teamcity/src/builds/test/QuickTests.kt b/.teamcity/src/builds/test/QuickTests.kt index 5b1d2541480ad3..a294fce9599c36 100644 --- a/.teamcity/src/builds/test/QuickTests.kt +++ b/.teamcity/src/builds/test/QuickTests.kt @@ -12,7 +12,7 @@ object QuickTests : BuildType({ kibanaAgent(2) val testScripts = mapOf( - "Test Hardening" to ".ci/teamcity/checkes/test_hardening.sh", + "Test Hardening" to ".ci/teamcity/checks/test_hardening.sh", "Test Projects" to ".ci/teamcity/tests/test_projects.sh", "Mocha Tests" to ".ci/teamcity/tests/mocha.sh" ) diff --git a/.teamcity/src/projects/Kibana.kt b/.teamcity/src/projects/Kibana.kt index 20c30eedf5b91d..1878f49debe8c1 100644 --- a/.teamcity/src/projects/Kibana.kt +++ b/.teamcity/src/projects/Kibana.kt @@ -5,9 +5,10 @@ import builds.* import builds.default.* import builds.oss.* import builds.test.* +import CloudProfile +import co.elastic.teamcity.common.googleCloudProfile import jetbrains.buildServer.configs.kotlin.v2019_2.* import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.slackConnection -import kibanaAgent import templates.KibanaTemplate import templates.DefaultTemplate import vcs.Elasticsearch @@ -31,7 +32,7 @@ fun Kibana(config: KibanaConfiguration = KibanaConfiguration()) : Project { param("teamcity.ui.settings.readOnly", "true") // https://github.com/JetBrains/teamcity-webhooks - param("teamcity.internal.webhooks.enable", "true") + param("teamcity.internal.webhooks.enable", "false") param("teamcity.internal.webhooks.events", "BUILD_STARTED;BUILD_FINISHED;BUILD_INTERRUPTED;CHANGES_LOADED;BUILD_TYPE_ADDED_TO_QUEUE;BUILD_PROBLEMS_CHANGED") param("teamcity.internal.webhooks.url", "https://ci-stats.kibana.dev/_teamcity_webhook") param("teamcity.internal.webhooks.username", "automation") @@ -46,36 +47,9 @@ fun Kibana(config: KibanaConfiguration = KibanaConfiguration()) : Project { defaultTemplate = DefaultTemplate - features { - val sizes = listOf("2", "4", "8", "16") - for (size in sizes) { - kibanaAgent(size) - } - - kibanaAgent { - id = "KIBANA_C2_16" - param("source-id", "kibana-c2-16-") - param("machineType", "c2-standard-16") - } - - feature { - id = "kibana" - type = "CloudProfile" - param("agentPushPreset", "") - param("profileId", "kibana") - param("profileServerUrl", "") - param("name", "kibana") - param("total-work-time", "") - param("credentialsType", "key") - param("description", "") - param("next-hour", "") - param("cloud-code", "google") - param("terminate-after-build", "true") - param("terminate-idle-time", "30") - param("enabled", "true") - param("secure:accessKey", "credentialsJSON:447fdd4d-7129-46b7-9822-2e57658c7422") - } + googleCloudProfile(CloudProfile) + features { slackConnection { id = "KIBANA_SLACK" displayName = "Kibana Slack" @@ -106,7 +80,6 @@ fun Kibana(config: KibanaConfiguration = KibanaConfiguration()) : Project { buildType(JestIntegration) } - buildType(ApiServerIntegration) buildType(QuickTests) buildType(AllTests) } @@ -125,6 +98,7 @@ fun Kibana(config: KibanaConfiguration = KibanaConfiguration()) : Project { buildType(OssFirefox) buildType(OssAccessibility) buildType(OssPluginFunctional) + buildType(OssApiServerIntegration) subProject { id("CIGroups") diff --git a/.teamcity/src/templates/DefaultTemplate.kt b/.teamcity/src/templates/DefaultTemplate.kt index 762218b72ab107..1f7f364600e212 100644 --- a/.teamcity/src/templates/DefaultTemplate.kt +++ b/.teamcity/src/templates/DefaultTemplate.kt @@ -1,15 +1,14 @@ package templates +import StandardAgents +import co.elastic.teamcity.common.requireAgent import jetbrains.buildServer.configs.kotlin.v2019_2.Template import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.perfmon object DefaultTemplate : Template({ name = "Default Template" - requirements { - equals("system.cloud.profile_id", "kibana", "RQ_CLOUD_PROFILE_ID") - startsWith("teamcity.agent.name", "kibana-standard-2-", "RQ_AGENT_NAME") - } + requireAgent(StandardAgents["2"]!!) params { param("env.HOME", "/var/lib/jenkins") // TODO once the agent images are sorted out diff --git a/.teamcity/src/templates/KibanaTemplate.kt b/.teamcity/src/templates/KibanaTemplate.kt index 117c30ddb86e31..83fe4fdaa1edd1 100644 --- a/.teamcity/src/templates/KibanaTemplate.kt +++ b/.teamcity/src/templates/KibanaTemplate.kt @@ -1,5 +1,7 @@ package templates +import StandardAgents +import co.elastic.teamcity.common.requireAgent import vcs.Kibana import jetbrains.buildServer.configs.kotlin.v2019_2.BuildStep import jetbrains.buildServer.configs.kotlin.v2019_2.ParameterDisplay @@ -21,10 +23,7 @@ object KibanaTemplate : Template({ // checkoutDir = "/dev/shm/%system.teamcity.buildType.id%/%system.build.number%/kibana" } - requirements { - equals("system.cloud.profile_id", "kibana", "RQ_CLOUD_PROFILE_ID") - startsWith("teamcity.agent.name", "kibana-standard-2-", "RQ_AGENT_NAME") - } + requireAgent(StandardAgents["2"]!!) features { perfmon { } @@ -41,7 +40,7 @@ object KibanaTemplate : Template({ } failureConditions { - executionTimeoutMin = 120 + executionTimeoutMin = 160 testFailure = false } diff --git a/.teamcity/tests/projects/KibanaTest.kt b/.teamcity/tests/projects/KibanaTest.kt index 677effec5be658..311c15a1da7cbd 100644 --- a/.teamcity/tests/projects/KibanaTest.kt +++ b/.teamcity/tests/projects/KibanaTest.kt @@ -1,5 +1,7 @@ package projects +import jetbrains.buildServer.configs.kotlin.v2019_2.AbsoluteId +import jetbrains.buildServer.configs.kotlin.v2019_2.DslContext import org.junit.Assert.* import org.junit.Test @@ -18,10 +20,11 @@ class KibanaTest { @Test fun test_CloudImages_Exist() { + DslContext.projectId = AbsoluteId("My Project") val project = Kibana(TestConfig) assertTrue(project.features.items.any { - it.type == "CloudImage" && it.params.any { param -> param.name == "network" && param.value == "network"} + it.type == "CloudImage" && it.params.any { param -> param.name == "network" && param.value == "teamcity" } }) } } diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md index 40fc1a8e05a68b..7c53356615ee9f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md @@ -9,7 +9,7 @@ Signature: ```typescript -export interface LegacyElasticsearchError extends Boom +export interface LegacyElasticsearchError extends Boom.Boom ``` ## Properties diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.apply_filter_trigger.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.apply_filter_trigger.md new file mode 100644 index 00000000000000..aaed18b3b88907 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.apply_filter_trigger.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [APPLY\_FILTER\_TRIGGER](./kibana-plugin-plugins-data-public.apply_filter_trigger.md) + +## APPLY\_FILTER\_TRIGGER variable + +Signature: + +```typescript +APPLY_FILTER_TRIGGER = "FILTER_TRIGGER" +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.embeddable.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.embeddable.md index 027ae4209b77fd..dbeeeb9979aae3 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.embeddable.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.embeddable.md @@ -7,5 +7,5 @@ Signature: ```typescript -embeddable?: IEmbeddable; +embeddable?: unknown; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.md index 62817cd0a1e33f..2f844b6844645d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.md @@ -14,7 +14,7 @@ export interface ApplyGlobalFilterActionContext | Property | Type | Description | | --- | --- | --- | -| [embeddable](./kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.embeddable.md) | IEmbeddable | | +| [embeddable](./kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.embeddable.md) | unknown | | | [filters](./kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.filters.md) | Filter[] | | | [timeFieldName](./kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.timefieldname.md) | string | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.allownoindex.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.allownoindex.md new file mode 100644 index 00000000000000..5e397d11b0a89e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.allownoindex.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [allowNoIndex](./kibana-plugin-plugins-data-public.indexpattern.allownoindex.md) + +## IndexPattern.allowNoIndex property + +prevents errors when index pattern exists before indices + +Signature: + +```typescript +readonly allowNoIndex: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md index a370341000960e..b318427012c0ac 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md @@ -19,6 +19,7 @@ getAsSavedObjectBody(): { fieldFormatMap: string | undefined; type: string | undefined; typeMeta: string | undefined; + allowNoIndex: true | undefined; }; ``` Returns: @@ -33,5 +34,6 @@ getAsSavedObjectBody(): { fieldFormatMap: string | undefined; type: string | undefined; typeMeta: string | undefined; + allowNoIndex: true | undefined; }` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md index 179148265e68d3..b640ef1b89606b 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md @@ -20,6 +20,7 @@ export declare class IndexPattern implements IIndexPattern | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [allowNoIndex](./kibana-plugin-plugins-data-public.indexpattern.allownoindex.md) | | boolean | prevents errors when index pattern exists before indices | | [deleteFieldFormat](./kibana-plugin-plugins-data-public.indexpattern.deletefieldformat.md) | | (fieldName: string) => void | | | [fieldAttrs](./kibana-plugin-plugins-data-public.indexpattern.fieldattrs.md) | | FieldAttrs | | | [fieldFormatMap](./kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md) | | Record<string, any> | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.allownoindex.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.allownoindex.md new file mode 100644 index 00000000000000..9438f38194493d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.allownoindex.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternAttributes](./kibana-plugin-plugins-data-public.indexpatternattributes.md) > [allowNoIndex](./kibana-plugin-plugins-data-public.indexpatternattributes.allownoindex.md) + +## IndexPatternAttributes.allowNoIndex property + +prevents errors when index pattern exists before indices + +Signature: + +```typescript +allowNoIndex?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md index c5ea38278e8208..1bbede5658942a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md @@ -14,6 +14,7 @@ export interface IndexPatternAttributes | Property | Type | Description | | --- | --- | --- | +| [allowNoIndex](./kibana-plugin-plugins-data-public.indexpatternattributes.allownoindex.md) | boolean | prevents errors when index pattern exists before indices | | [fieldAttrs](./kibana-plugin-plugins-data-public.indexpatternattributes.fieldattrs.md) | string | | | [fieldFormatMap](./kibana-plugin-plugins-data-public.indexpatternattributes.fieldformatmap.md) | string | | | [fields](./kibana-plugin-plugins-data-public.indexpatternattributes.fields.md) | string | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.allownoindex.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.allownoindex.md new file mode 100644 index 00000000000000..50adef8268694b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.allownoindex.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [allowNoIndex](./kibana-plugin-plugins-data-public.indexpatternspec.allownoindex.md) + +## IndexPatternSpec.allowNoIndex property + +Signature: + +```typescript +allowNoIndex?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md index 06917fcac1b4dc..9357ad7d5077e9 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md @@ -14,6 +14,7 @@ export interface IndexPatternSpec | Property | Type | Description | | --- | --- | --- | +| [allowNoIndex](./kibana-plugin-plugins-data-public.indexpatternspec.allownoindex.md) | boolean | | | [fieldAttrs](./kibana-plugin-plugins-data-public.indexpatternspec.fieldattrs.md) | FieldAttrs | | | [fieldFormats](./kibana-plugin-plugins-data-public.indexpatternspec.fieldformats.md) | Record<string, SerializedFieldFormat> | | | [fields](./kibana-plugin-plugins-data-public.indexpatternspec.fields.md) | IndexPatternFieldMap | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 8de3821161ab43..2040043d4351b9 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -102,6 +102,7 @@ | [ACTION\_GLOBAL\_APPLY\_FILTER](./kibana-plugin-plugins-data-public.action_global_apply_filter.md) | | | [AggGroupLabels](./kibana-plugin-plugins-data-public.agggrouplabels.md) | | | [AggGroupNames](./kibana-plugin-plugins-data-public.agggroupnames.md) | | +| [APPLY\_FILTER\_TRIGGER](./kibana-plugin-plugins-data-public.apply_filter_trigger.md) | | | [baseFormattersPublic](./kibana-plugin-plugins-data-public.baseformatterspublic.md) | | | [castEsToKbnFieldTypeName](./kibana-plugin-plugins-data-public.castestokbnfieldtypename.md) | Get the KbnFieldType name for an esType string | | [connectToQueryState](./kibana-plugin-plugins-data-public.connecttoquerystate.md) | Helper to setup two-way syncing of global data and a state container | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.allownoindex.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.allownoindex.md new file mode 100644 index 00000000000000..fe7bec70196c85 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.allownoindex.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [allowNoIndex](./kibana-plugin-plugins-data-server.indexpattern.allownoindex.md) + +## IndexPattern.allowNoIndex property + +prevents errors when index pattern exists before indices + +Signature: + +```typescript +readonly allowNoIndex: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md index 274a475872b0bc..7d70af4b535fea 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md @@ -19,6 +19,7 @@ getAsSavedObjectBody(): { fieldFormatMap: string | undefined; type: string | undefined; typeMeta: string | undefined; + allowNoIndex: true | undefined; }; ``` Returns: @@ -33,5 +34,6 @@ getAsSavedObjectBody(): { fieldFormatMap: string | undefined; type: string | undefined; typeMeta: string | undefined; + allowNoIndex: true | undefined; }` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md index b2cb217fecaa20..54f020e57cf4a6 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md @@ -20,6 +20,7 @@ export declare class IndexPattern implements IIndexPattern | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [allowNoIndex](./kibana-plugin-plugins-data-server.indexpattern.allownoindex.md) | | boolean | prevents errors when index pattern exists before indices | | [deleteFieldFormat](./kibana-plugin-plugins-data-server.indexpattern.deletefieldformat.md) | | (fieldName: string) => void | | | [fieldAttrs](./kibana-plugin-plugins-data-server.indexpattern.fieldattrs.md) | | FieldAttrs | | | [fieldFormatMap](./kibana-plugin-plugins-data-server.indexpattern.fieldformatmap.md) | | Record<string, any> | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.allownoindex.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.allownoindex.md new file mode 100644 index 00000000000000..1255a6fe9f0cae --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.allownoindex.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) > [allowNoIndex](./kibana-plugin-plugins-data-server.indexpatternattributes.allownoindex.md) + +## IndexPatternAttributes.allowNoIndex property + +prevents errors when index pattern exists before indices + +Signature: + +```typescript +allowNoIndex?: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md index 6559b4d7110bea..b9b9f955c7ab59 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md @@ -14,6 +14,7 @@ export interface IndexPatternAttributes | Property | Type | Description | | --- | --- | --- | +| [allowNoIndex](./kibana-plugin-plugins-data-server.indexpatternattributes.allownoindex.md) | boolean | prevents errors when index pattern exists before indices | | [fieldAttrs](./kibana-plugin-plugins-data-server.indexpatternattributes.fieldattrs.md) | string | | | [fieldFormatMap](./kibana-plugin-plugins-data-server.indexpatternattributes.fieldformatmap.md) | string | | | [fields](./kibana-plugin-plugins-data-server.indexpatternattributes.fields.md) | string | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md index b90018c3d9cdd9..bd90f23b4ab590 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md @@ -11,7 +11,7 @@ setup(core: CoreSetup, { bfetch, e __enhance: (enhancements: DataEnhancements) => void; search: ISearchSetup; fieldFormats: { - register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number; + register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number; }; }; ``` @@ -29,7 +29,7 @@ setup(core: CoreSetup, { bfetch, e __enhance: (enhancements: DataEnhancements) => void; search: ISearchSetup; fieldFormats: { - register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number; + register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number; }; }` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index 8a3dbe5a6350c1..88f85eb7a7d05e 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -12,7 +12,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -31,7 +31,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.embeddable.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.embeddable.md index 06e51958a2d1e5..92926d10a543c9 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.embeddable.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.embeddable.md @@ -7,5 +7,5 @@ Signature: ```typescript -embeddable: IEmbeddable; +embeddable: T; ``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.md index a2c2d9245eabe3..753a3ff2ec6ec9 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.md @@ -7,12 +7,12 @@ Signature: ```typescript -export interface EmbeddableContext +export interface EmbeddableContext ``` ## Properties | Property | Type | Description | | --- | --- | --- | -| [embeddable](./kibana-plugin-plugins-embeddable-public.embeddablecontext.embeddable.md) | IEmbeddable | | +| [embeddable](./kibana-plugin-plugins-embeddable-public.embeddablecontext.embeddable.md) | T | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md index f36f7b4ee77a4c..0f14215ff13090 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md @@ -16,9 +16,6 @@ export declare type EmbeddableInput = { enhancements?: SerializableState; disabledActions?: string[]; disableTriggers?: boolean; - timeRange?: TimeRange; - query?: Query; - filters?: Filter[]; searchSessionId?: string; }; ``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.data.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.data.md deleted file mode 100644 index d3a62657372ac1..00000000000000 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.data.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableSetupDependencies](./kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.md) > [data](./kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.data.md) - -## EmbeddableSetupDependencies.data property - -Signature: - -```typescript -data: DataPublicPluginSetup; -``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.md index fdd31ca75be2a7..957e3f279ff602 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.md @@ -14,6 +14,5 @@ export interface EmbeddableSetupDependencies | Property | Type | Description | | --- | --- | --- | -| [data](./kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.data.md) | DataPublicPluginSetup | | | [uiActions](./kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.uiactions.md) | UiActionsSetup | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.data.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.data.md deleted file mode 100644 index 0595609b11e498..00000000000000 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.data.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableStartDependencies](./kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.md) > [data](./kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.data.md) - -## EmbeddableStartDependencies.data property - -Signature: - -```typescript -data: DataPublicPluginStart; -``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.md index 5a1b5d1e068610..342163ed2e4137 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.md @@ -14,7 +14,6 @@ export interface EmbeddableStartDependencies | Property | Type | Description | | --- | --- | --- | -| [data](./kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.data.md) | DataPublicPluginStart | | | [inspector](./kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.inspector.md) | InspectorStart | | | [uiActions](./kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.uiactions.md) | UiActionsStart | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md index 276499b435e1f8..77e9c2d00b2dd8 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md @@ -9,7 +9,7 @@ Constructs a new instance of the `EmbeddableStateTransfer` class Signature: ```typescript -constructor(navigateToApp: ApplicationStart['navigateToApp'], appList?: ReadonlyMap | undefined, customStorage?: Storage); +constructor(navigateToApp: ApplicationStart['navigateToApp'], currentAppId$: ApplicationStart['currentAppId$'], appList?: ReadonlyMap | undefined, customStorage?: Storage); ``` ## Parameters @@ -17,6 +17,7 @@ constructor(navigateToApp: ApplicationStart['navigateToApp'], appList?: Readonly | Parameter | Type | Description | | --- | --- | --- | | navigateToApp | ApplicationStart['navigateToApp'] | | +| currentAppId$ | ApplicationStart['currentAppId$'] | | | appList | ReadonlyMap<string, PublicAppInfo> | undefined | | | customStorage | Storage | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.istransferinprogress.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.istransferinprogress.md new file mode 100644 index 00000000000000..f00d015f316d2a --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.istransferinprogress.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableStateTransfer](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md) > [isTransferInProgress](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.istransferinprogress.md) + +## EmbeddableStateTransfer.isTransferInProgress property + +Signature: + +```typescript +isTransferInProgress: boolean; +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md index 3676b744b8cc9c..76b6708b93bd12 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md @@ -16,13 +16,14 @@ export declare class EmbeddableStateTransfer | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(navigateToApp, appList, customStorage)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md) | | Constructs a new instance of the EmbeddableStateTransfer class | +| [(constructor)(navigateToApp, currentAppId$, appList, customStorage)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md) | | Constructs a new instance of the EmbeddableStateTransfer class | ## Properties | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [getAppNameFromId](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getappnamefromid.md) | | (appId: string) => string | undefined | Fetches an internationalized app title when given an appId. | +| [isTransferInProgress](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.istransferinprogress.md) | | boolean | | ## Methods diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md index 62610624655a14..2f5966f9ba940d 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md @@ -7,5 +7,5 @@ Signature: ```typescript -isContextMenuTriggerContext: (context: unknown) => context is EmbeddableContext +isContextMenuTriggerContext: (context: unknown) => context is EmbeddableContext> ``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md index a6aeba23cd280c..b875b1fce42887 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md @@ -86,6 +86,8 @@ | [PANEL\_NOTIFICATION\_TRIGGER](./kibana-plugin-plugins-embeddable-public.panel_notification_trigger.md) | | | [panelBadgeTrigger](./kibana-plugin-plugins-embeddable-public.panelbadgetrigger.md) | | | [panelNotificationTrigger](./kibana-plugin-plugins-embeddable-public.panelnotificationtrigger.md) | | +| [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-embeddable-public.select_range_trigger.md) | | +| [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-embeddable-public.value_click_trigger.md) | | | [withEmbeddableSubscription](./kibana-plugin-plugins-embeddable-public.withembeddablesubscription.md) | | ## Type Aliases diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.select_range_trigger.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.select_range_trigger.md new file mode 100644 index 00000000000000..175e3fe947a0f9 --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.select_range_trigger.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-embeddable-public.select_range_trigger.md) + +## SELECT\_RANGE\_TRIGGER variable + +Signature: + +```typescript +SELECT_RANGE_TRIGGER = "SELECT_RANGE_TRIGGER" +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.value_click_trigger.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.value_click_trigger.md new file mode 100644 index 00000000000000..a85be3142d0f2b --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.value_click_trigger.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-embeddable-public.value_click_trigger.md) + +## VALUE\_CLICK\_TRIGGER variable + +Signature: + +```typescript +VALUE_CLICK_TRIGGER = "VALUE_CLICK_TRIGGER" +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md index 931e474a410065..c22c8bc6b62456 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md @@ -20,6 +20,6 @@ export interface IInterpreterRenderHandlers | [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md) | (event: any) => Promise<boolean> | | | [onDestroy](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.ondestroy.md) | (fn: () => void) => void | | | [reload](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.reload.md) | () => void | | -| [uiState](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md) | PersistedState | | +| [uiState](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md) | unknown | This uiState interface is actually PersistedState from the visualizations plugin, but expressions cannot know about vis or it creates a mess of circular dependencies. Downstream consumers of the uiState handler will need to cast for now. | | [update](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.update.md) | (params: any) => void | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md index 8d74c8e555fee6..461bf861d4d5ee 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md @@ -4,8 +4,10 @@ ## IInterpreterRenderHandlers.uiState property +This uiState interface is actually `PersistedState` from the visualizations plugin, but expressions cannot know about vis or it creates a mess of circular dependencies. Downstream consumers of the uiState handler will need to cast for now. + Signature: ```typescript -uiState?: PersistedState; +uiState?: unknown; ``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md index 273703cacca063..547608f40e6aa2 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md @@ -20,6 +20,6 @@ export interface IInterpreterRenderHandlers | [hasCompatibleActions](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md) | (event: any) => Promise<boolean> | | | [onDestroy](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.ondestroy.md) | (fn: () => void) => void | | | [reload](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.reload.md) | () => void | | -| [uiState](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md) | PersistedState | | +| [uiState](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md) | unknown | This uiState interface is actually PersistedState from the visualizations plugin, but expressions cannot know about vis or it creates a mess of circular dependencies. Downstream consumers of the uiState handler will need to cast for now. | | [update](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.update.md) | (params: any) => void | | diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md index b09433c6454adb..ca1c8eec8c2f77 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md @@ -4,8 +4,10 @@ ## IInterpreterRenderHandlers.uiState property +This uiState interface is actually `PersistedState` from the visualizations plugin, but expressions cannot know about vis or it creates a mess of circular dependencies. Downstream consumers of the uiState handler will need to cast for now. + Signature: ```typescript -uiState?: PersistedState; +uiState?: unknown; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.apply_filter_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.apply_filter_trigger.md deleted file mode 100644 index 94e66bf404f5ce..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.apply_filter_trigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [APPLY\_FILTER\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.apply_filter_trigger.md) - -## APPLY\_FILTER\_TRIGGER variable - -Signature: - -```typescript -APPLY_FILTER_TRIGGER = "FILTER_TRIGGER" -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.applyfiltertrigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.applyfiltertrigger.md deleted file mode 100644 index e1fb6d342457e8..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.applyfiltertrigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [applyFilterTrigger](./kibana-plugin-plugins-ui_actions-public.applyfiltertrigger.md) - -## applyFilterTrigger variable - -Signature: - -```typescript -applyFilterTrigger: Trigger<'FILTER_TRIGGER'> -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md index fd1ea7df4fb745..76e347bddd1682 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md @@ -41,14 +41,8 @@ | [ACTION\_VISUALIZE\_FIELD](./kibana-plugin-plugins-ui_actions-public.action_visualize_field.md) | | | [ACTION\_VISUALIZE\_GEO\_FIELD](./kibana-plugin-plugins-ui_actions-public.action_visualize_geo_field.md) | | | [ACTION\_VISUALIZE\_LENS\_FIELD](./kibana-plugin-plugins-ui_actions-public.action_visualize_lens_field.md) | | -| [APPLY\_FILTER\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.apply_filter_trigger.md) | | -| [applyFilterTrigger](./kibana-plugin-plugins-ui_actions-public.applyfiltertrigger.md) | | | [ROW\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.row_click_trigger.md) | | | [rowClickTrigger](./kibana-plugin-plugins-ui_actions-public.rowclicktrigger.md) | | -| [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.select_range_trigger.md) | | -| [selectRangeTrigger](./kibana-plugin-plugins-ui_actions-public.selectrangetrigger.md) | | -| [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.value_click_trigger.md) | | -| [valueClickTrigger](./kibana-plugin-plugins-ui_actions-public.valueclicktrigger.md) | | | [VISUALIZE\_FIELD\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.visualize_field_trigger.md) | | | [VISUALIZE\_GEO\_FIELD\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.visualize_geo_field_trigger.md) | | | [visualizeFieldTrigger](./kibana-plugin-plugins-ui_actions-public.visualizefieldtrigger.md) | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md index e8baf44ff9cbc3..a75637e8ea9d34 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md @@ -7,5 +7,5 @@ Signature: ```typescript -embeddable?: IEmbeddable; +embeddable?: unknown; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md index 74b55d85f10e31..b69734cfc3233d 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md @@ -15,5 +15,5 @@ export interface RowClickContext | Property | Type | Description | | --- | --- | --- | | [data](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.data.md) | {
rowIndex: number;
table: Datatable;
columns?: string[];
} | | -| [embeddable](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md) | IEmbeddable | | +| [embeddable](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md) | unknown | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.select_range_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.select_range_trigger.md deleted file mode 100644 index fd784ff17fa840..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.select_range_trigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.select_range_trigger.md) - -## SELECT\_RANGE\_TRIGGER variable - -Signature: - -```typescript -SELECT_RANGE_TRIGGER = "SELECT_RANGE_TRIGGER" -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.selectrangetrigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.selectrangetrigger.md deleted file mode 100644 index 0d9fa2d83ee57a..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.selectrangetrigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [selectRangeTrigger](./kibana-plugin-plugins-ui_actions-public.selectrangetrigger.md) - -## selectRangeTrigger variable - -Signature: - -```typescript -selectRangeTrigger: Trigger<'SELECT_RANGE_TRIGGER'> -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.id.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.id.md index 426f17f9a03529..5603c852ad39db 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.id.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.id.md @@ -4,7 +4,7 @@ ## Trigger.id property -Unique name of the trigger as identified in `ui_actions` plugin trigger registry, such as "SELECT\_RANGE\_TRIGGER" or "VALUE\_CLICK\_TRIGGER". +Unique name of the trigger as identified in `ui_actions` plugin trigger registry. Signature: diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.md index b69bba892f4753..ed76cfea976845 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.md @@ -21,6 +21,6 @@ export interface Trigger | Property | Type | Description | | --- | --- | --- | | [description](./kibana-plugin-plugins-ui_actions-public.trigger.description.md) | string | A longer user friendly description of the trigger. | -| [id](./kibana-plugin-plugins-ui_actions-public.trigger.id.md) | ID | Unique name of the trigger as identified in ui_actions plugin trigger registry, such as "SELECT\_RANGE\_TRIGGER" or "VALUE\_CLICK\_TRIGGER". | +| [id](./kibana-plugin-plugins-ui_actions-public.trigger.id.md) | ID | Unique name of the trigger as identified in ui_actions plugin trigger registry. | | [title](./kibana-plugin-plugins-ui_actions-public.trigger.title.md) | string | User friendly name of the trigger. | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.filter_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.filter_trigger.md deleted file mode 100644 index 0ccf8aa3d7415a..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.filter_trigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [TriggerContextMapping](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md) > [FILTER\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.filter_trigger.md) - -## TriggerContextMapping.FILTER\_TRIGGER property - -Signature: - -```typescript -[APPLY_FILTER_TRIGGER]: ApplyGlobalFilterActionContext; -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md index 2f0d22cf6dd741..da7a7a8bfe6452 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md @@ -15,10 +15,7 @@ export interface TriggerContextMapping | Property | Type | Description | | --- | --- | --- | | [""](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.__.md) | TriggerContext | | -| [FILTER\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.filter_trigger.md) | ApplyGlobalFilterActionContext | | | [ROW\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.row_click_trigger.md) | RowClickContext | | -| [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.select_range_trigger.md) | RangeSelectContext | | -| [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.value_click_trigger.md) | ValueClickContext | | | [VISUALIZE\_FIELD\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.visualize_field_trigger.md) | VisualizeFieldContext | | | [VISUALIZE\_GEO\_FIELD\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.visualize_geo_field_trigger.md) | VisualizeFieldContext | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.select_range_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.select_range_trigger.md deleted file mode 100644 index c5ef6843390b34..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.select_range_trigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [TriggerContextMapping](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md) > [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.select_range_trigger.md) - -## TriggerContextMapping.SELECT\_RANGE\_TRIGGER property - -Signature: - -```typescript -[SELECT_RANGE_TRIGGER]: RangeSelectContext; -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.value_click_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.value_click_trigger.md deleted file mode 100644 index 129144a66cee56..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.value_click_trigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [TriggerContextMapping](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md) > [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.value_click_trigger.md) - -## TriggerContextMapping.VALUE\_CLICK\_TRIGGER property - -Signature: - -```typescript -[VALUE_CLICK_TRIGGER]: ValueClickContext; -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md index ca999322b7a567..f29d487d774e0d 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md @@ -11,5 +11,5 @@ Signature: ```typescript -readonly addTriggerAction: (triggerId: T, action: ActionDefinition | Action) => void; +readonly addTriggerAction: (triggerId: T, action: ActionDefinition | Action) => void; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md index e95e7e1eb38b61..1ebb30c49c0b32 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly attachAction: (triggerId: T, actionId: string) => void; +readonly attachAction: (triggerId: T, actionId: string) => void; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md index 8e7fb8b8bbf29f..b20f08520c43d5 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md @@ -12,5 +12,5 @@ Signature: ```typescript -readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise; +readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md index d540de76374413..300c46a47c47f6 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">; +readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel">; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md index b996620686a282..95b737a8d6cae0 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getTrigger: (triggerId: T) => TriggerContract; +readonly getTrigger: (triggerId: T) => TriggerContract; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md index f94b34ecc2d908..27c1b1eb48f165 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getTriggerActions: (triggerId: T) => Action[]; +readonly getTriggerActions: (triggerId: T) => Action[]; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md index dff958608ef9e0..edb7d2d3a15516 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; +readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md index e35eb503ab62b5..4fe8431770deae 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md @@ -21,19 +21,19 @@ export declare class UiActionsService | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [actions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.actions.md) | | ActionRegistry | | -| [addTriggerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, action: ActionDefinition<TriggerContextMapping[T]> | Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">) => void | addTriggerAction is similar to attachAction as it attaches action to a trigger, but it also registers the action, if it has not been registered, yet.addTriggerAction also infers better typing of the action argument. | -| [attachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, actionId: string) => void | | +| [addTriggerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md) | | <T extends "" | "ROW_CLICK_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "FILTER_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER">(triggerId: T, action: ActionDefinition<TriggerContextMapping[T]> | Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel">) => void | addTriggerAction is similar to attachAction as it attaches action to a trigger, but it also registers the action, if it has not been registered, yet.addTriggerAction also infers better typing of the action argument. | +| [attachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md) | | <T extends "" | "ROW_CLICK_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "FILTER_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER">(triggerId: T, actionId: string) => void | | | [clear](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.clear.md) | | () => void | Removes all registered triggers and actions. | | [detachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.detachaction.md) | | (triggerId: TriggerId, actionId: string) => void | | -| [executeTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContext<T>) => Promise<void> | | +| [executeTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md) | | <T extends "" | "ROW_CLICK_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "FILTER_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER">(triggerId: T, context: TriggerContext<T>) => Promise<void> | | | [executionService](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executionservice.md) | | UiActionsExecutionService | | | [fork](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.fork.md) | | () => UiActionsService | "Fork" a separate instance of UiActionsService that inherits all existing triggers and actions, but going forward all new triggers and actions added to this instance of UiActionsService are only available within this instance. | -| [getAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md) | | <T extends ActionDefinition<{}>>(id: string) => Action<ActionContext<T>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV"> | | -| [getTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => TriggerContract<T> | | -| [getTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">[] | | -| [getTriggerCompatibleActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContextMapping[T]) => Promise<Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">[]> | | +| [getAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md) | | <T extends ActionDefinition<{}>>(id: string) => Action<ActionContext<T>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel"> | | +| [getTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md) | | <T extends "" | "ROW_CLICK_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "FILTER_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER">(triggerId: T) => TriggerContract<T> | | +| [getTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md) | | <T extends "" | "ROW_CLICK_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "FILTER_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER">(triggerId: T) => Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel">[] | | +| [getTriggerCompatibleActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md) | | <T extends "" | "ROW_CLICK_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "FILTER_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER">(triggerId: T, context: TriggerContextMapping[T]) => Promise<Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel">[]> | | | [hasAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.hasaction.md) | | (actionId: string) => boolean | | -| [registerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md) | | <A extends ActionDefinition<{}>>(definition: A) => Action<ActionContext<A>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV"> | | +| [registerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md) | | <A extends ActionDefinition<{}>>(definition: A) => Action<ActionContext<A>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel"> | | | [registerTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registertrigger.md) | | (trigger: Trigger) => void | | | [triggers](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.triggers.md) | | TriggerRegistry | | | [triggerToActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.triggertoactions.md) | | TriggerToActionsRegistry | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md index 6f03777e14552a..dee5f75f7c074d 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">; +readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel">; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.value_click_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.value_click_trigger.md deleted file mode 100644 index bd8d4dc50b8fdf..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.value_click_trigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.value_click_trigger.md) - -## VALUE\_CLICK\_TRIGGER variable - -Signature: - -```typescript -VALUE_CLICK_TRIGGER = "VALUE_CLICK_TRIGGER" -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.valueclicktrigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.valueclicktrigger.md deleted file mode 100644 index 5c4fc284d83b1c..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.valueclicktrigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [valueClickTrigger](./kibana-plugin-plugins-ui_actions-public.valueclicktrigger.md) - -## valueClickTrigger variable - -Signature: - -```typescript -valueClickTrigger: Trigger<'VALUE_CLICK_TRIGGER'> -``` diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 9a87d4c9d886ac..99fadb240335a5 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -453,8 +453,8 @@ of buckets to try to represent. ==== Visualization [horizontal] -[[visualization-visualize-chartslibrary]]`visualization:visualize:chartsLibrary`:: -Enables the new charts library for area, line, and bar charts in visualization panels. Does *not* support the split chart aggregation. +[[visualization-visualize-chartslibrary]]`visualization:visualize:legacyChartsLibrary`:: +Enables legacy charts library for area, line and bar charts in visualize. Currently, only legacy charts library supports split chart aggregation. [[visualization-colormapping]]`visualization:colorMapping`:: **This setting is deprecated and will not be supported as of 8.0.** diff --git a/docs/user/security/audit-logging.asciidoc b/docs/user/security/audit-logging.asciidoc index 7facde28e956f9..acb0f94cf878cb 100644 --- a/docs/user/security/audit-logging.asciidoc +++ b/docs/user/security/audit-logging.asciidoc @@ -47,9 +47,11 @@ For information on how to configure `xpack.security.audit.appender`, refer to Refer to the table of events that can be logged for auditing purposes. -Each event is broken down into `category`, `type`, `action` and `outcome` fields +Each event is broken down into <>, <>, <> and <> fields to make it easy to filter, query and aggregate the resulting logs. +Refer to <> for a table of fields that get logged with audit event. + [NOTE] ============================================================================ To ensure that a record of every operation is persisted even in case of an @@ -230,3 +232,188 @@ Refer to the corresponding {es} logs for potential write errors. | `http_request` | `unknown` | User is making an HTTP request. |====== + + +[[xpack-security-ecs-audit-schema]] +==== ECS audit schema + +Audit logs are written in JSON using https://www.elastic.co/guide/en/ecs/1.6/index.html[Elastic Common Schema (ECS)] specification. + +[cols="2*<"] +|====== + +2+a| ===== Base Fields + +| *Field* +| *Description* + +| `@timestamp` +| Time when the event was generated. + +Example: `2016-05-23T08:05:34.853Z` + +| `message` +| Human readable description of the event. + +2+a| ===== Event Fields + +| *Field* +| *Description* + +| [[field-event-action]] `event.action` +| The action captured by the event. + +Refer to <> for a table of possible actions. + +| [[field-event-category]] `event.category` +| High level category associated with the event. + +This field is closely related to `event.type`, which is used as a subcategory. + +Possible values: +`database`, +`web`, +`authentication` + +| [[field-event-type]] `event.type` +| Subcategory associated with the event. + +This field can be used along with the `event.category` field to enable filtering events down to a level appropriate for single visualization. + +Possible values: +`creation`, +`access`, +`change`, +`deletion` + +| [[field-event-outcome]] `event.outcome` +| Denotes whether the event represents a success or failure. + +Possible values: +`success`, +`failure`, +`unknown` + +2+a| ===== User Fields + +| *Field* +| *Description* + +| `user.name` +| Login name of the user. + +Example: `jdoe` + +| `user.roles[]` +| Set of user roles at the time of the event. + +Example: `[kibana_admin, reporting_user]` + +2+a| ===== Kibana Fields + +| *Field* +| *Description* + +| `kibana.space_id` +| ID of the space associated with the event. + +Example: `default` + +| `kibana.session_id` +| ID of the user session associated with the event. + +Each login attempt results in a unique session id. + +| `kibana.saved_object.type` +| Type of saved object associated with the event. + +Example: `dashboard` + +| `kibana.saved_object.id` +| ID of the saved object associated with the event. + +| `kibana.authentication_provider` +| Name of the authentication provider associated with the event. + +Example: `my-saml-provider` + +| `kibana.authentication_type` +| Type of the authentication provider associated with the event. + +Example: `saml` + +| `kibana.authentication_realm` +| Name of the Elasticsearch realm that has authenticated the user. + +Example: `native` + +| `kibana.lookup_realm` +| Name of the Elasticsearch realm where the user details were retrieved from. + +Example: `native` + +| `kibana.add_to_spaces[]` +| Set of space IDs that a saved object is being shared to as part of the event. + +Example: `[default, marketing]` + +| `kibana.delete_from_spaces[]` +| Set of space IDs that a saved object is being removed from as part of the event. + +Example: `[marketing]` + +2+a| ===== Error Fields + +| *Field* +| *Description* + +| `error.code` +| Error code describing the error. + +| `error.message` +| Error message. + +2+a| ===== HTTP and URL Fields + +| *Field* +| *Description* + +| `http.request.method` +| HTTP request method. + +Example: `get`, `post`, `put`, `delete` + +| `url.domain` +| Domain of the url. + +Example: `www.elastic.co` + +| `url.path` +| Path of the request. + +Example: `/search` + +| `url.port` +| Port of the request. + +Example: `443` + +| `url.query` +| The query field describes the query string of the request. + +Example: `q=elasticsearch` + +| `url.scheme` +| Scheme of the request. + +Example: `https` + +2+a| ===== Tracing Fields + +| *Field* +| *Description* + +| `trace.id` +| Unique identifier allowing events of the same transaction from {kib} and {es} to be be correlated. + +|====== diff --git a/docs/user/security/encryption-keys/index.asciidoc b/docs/user/security/encryption-keys/index.asciidoc new file mode 100644 index 00000000000000..58c0c0bb775caa --- /dev/null +++ b/docs/user/security/encryption-keys/index.asciidoc @@ -0,0 +1,44 @@ +[[kibana-encryption-keys]] +=== Set up encryptions keys to protect sensitive information + +The `kibana-encryption-keys` command helps you set up encryption keys that {kib} uses +to protect sensitive information. + +[discrete] +=== Synopsis + +[source,shell] +-------------------------------------------------- +bin/kibana-encryption-keys generate +[-i, --interactive] [-q, --quiet] +[-f, --force] [-h, --help] +-------------------------------------------------- + +[discrete] +=== Description + +{kib} uses encryption keys in several areas, ranging from encrypting data +in {kib} associated indices to storing session information. By defining these +encryption keys in your configuration, you'll ensure consistent operations +across restarts. + +[discrete] +[[encryption-key-parameters]] +=== Parameters + +`generate`:: Randomly generates passwords to the console. + +`-i, --interactive`:: Prompts you for which encryption keys to set and optionally +where to save a sample configuration file. + +`-q, --quiet`:: Outputs the encryption keys without helper information. + +`-f, --force`:: Shows help information. + +[discrete] +=== Examples + +[source,shell] +-------------------------------------------------- +bin/kibana-encryption-keys generate +-------------------------------------------------- diff --git a/docs/user/security/index.asciidoc b/docs/user/security/index.asciidoc index f84e9de87c734a..6a5c4a83aa3ad5 100644 --- a/docs/user/security/index.asciidoc +++ b/docs/user/security/index.asciidoc @@ -45,5 +45,6 @@ cause Kibana's authorization to behave unexpectedly. include::authorization/index.asciidoc[] include::authorization/kibana-privileges.asciidoc[] include::api-keys/index.asciidoc[] +include::encryption-keys/index.asciidoc[] include::role-mappings/index.asciidoc[] include::rbac_tutorial.asciidoc[] diff --git a/package.json b/package.json index b4b3cbe22b715d..6e8809063ca575 100644 --- a/package.json +++ b/package.json @@ -73,10 +73,6 @@ "url": "https://github.com/elastic/kibana.git" }, "resolutions": { - "**/@hapi/iron": "^5.1.4", - "**/@types/hapi__boom": "^7.4.1", - "**/@types/hapi__hapi": "^18.2.6", - "**/@types/hapi__mimos": "4.1.0", "**/@types/node": "14.14.14", "**/chokidar": "^3.4.3", "**/cross-fetch/node-fetch": "^2.6.1", @@ -97,7 +93,7 @@ "**/typescript": "4.1.2" }, "engines": { - "node": "14.15.2", + "node": "14.15.3", "yarn": "^1.21.1" }, "dependencies": { @@ -115,17 +111,17 @@ "@elastic/request-crypto": "1.1.4", "@elastic/safer-lodash-set": "link:packages/elastic-safer-lodash-set", "@elastic/search-ui-app-search-connector": "^1.5.0", - "@hapi/boom": "^7.4.11", - "@hapi/cookie": "^10.1.2", - "@hapi/good-squeeze": "5.2.1", - "@hapi/h2o2": "^8.3.2", - "@hapi/hapi": "^18.4.1", - "@hapi/hoek": "^8.5.1", - "@hapi/inert": "^5.2.2", - "@hapi/podium": "^3.4.3", - "@hapi/statehood": "^6.1.2", - "@hapi/vision": "^5.5.4", - "@hapi/wreck": "^15.0.2", + "@hapi/boom": "^9.1.1", + "@hapi/cookie": "^11.0.2", + "@hapi/good-squeeze": "6.0.0", + "@hapi/h2o2": "^9.0.2", + "@hapi/hapi": "^20.0.3", + "@hapi/hoek": "^9.1.0", + "@hapi/inert": "^6.0.3", + "@hapi/podium": "^4.1.1", + "@hapi/statehood": "^7.0.3", + "@hapi/vision": "^6.0.1", + "@hapi/wreck": "^17.1.0", "@kbn/ace": "link:packages/kbn-ace", "@kbn/analytics": "link:packages/kbn-analytics", "@kbn/apm-config-loader": "link:packages/kbn-apm-config-loader", @@ -381,6 +377,7 @@ "@mapbox/geojson-rewind": "^0.5.0", "@mapbox/mapbox-gl-draw": "^1.2.0", "@mapbox/mapbox-gl-rtl-text": "^0.2.3", + "@mapbox/vector-tile": "1.3.1", "@microsoft/api-documenter": "7.7.2", "@microsoft/api-extractor": "7.7.0", "@octokit/rest": "^16.35.0", @@ -451,14 +448,11 @@ "@types/graphql": "^0.13.2", "@types/gulp": "^4.0.6", "@types/gulp-zip": "^4.0.1", - "@types/hapi__boom": "^7.4.1", "@types/hapi__cookie": "^10.1.1", - "@types/hapi__h2o2": "8.3.0", - "@types/hapi__hapi": "^18.2.6", - "@types/hapi__hoek": "^6.2.0", - "@types/hapi__inert": "^5.2.1", + "@types/hapi__h2o2": "^8.3.2", + "@types/hapi__hapi": "^20.0.2", + "@types/hapi__inert": "^5.2.2", "@types/hapi__podium": "^3.4.1", - "@types/hapi__wreck": "^15.0.1", "@types/has-ansi": "^3.0.0", "@types/he": "^1.1.1", "@types/history": "^4.7.3", @@ -750,6 +744,7 @@ "ora": "^4.0.4", "p-limit": "^3.0.1", "parse-link-header": "^1.0.1", + "pbf": "3.2.1", "pirates": "^4.0.1", "pixelmatch": "^5.1.0", "pkg-up": "^2.0.0", diff --git a/packages/kbn-es/src/cluster.js b/packages/kbn-es/src/cluster.js index 68bcc37c65600a..eaf353b3e55d03 100644 --- a/packages/kbn-es/src/cluster.js +++ b/packages/kbn-es/src/cluster.js @@ -275,6 +275,15 @@ exports.Cluster = class Cluster { this._log.debug('%s %s', ES_BIN, args.join(' ')); + options.esEnvVars = options.esEnvVars || {}; + + // ES now automatically sets heap size to 50% of the machine's available memory + // so we need to set it to a smaller size for local dev and CI + // especially because we currently run many instances of ES on the same machine during CI + options.esEnvVars.ES_JAVA_OPTS = + (options.esEnvVars.ES_JAVA_OPTS ? `${options.esEnvVars.ES_JAVA_OPTS} ` : '') + + '-Xms1g -Xmx1g'; + this._process = execa(ES_BIN, args, { cwd: installPath, env: { diff --git a/src/core/server/elasticsearch/legacy/errors.ts b/src/core/server/elasticsearch/legacy/errors.ts index e557e7395fe566..adc1fa07287842 100644 --- a/src/core/server/elasticsearch/legacy/errors.ts +++ b/src/core/server/elasticsearch/legacy/errors.ts @@ -30,7 +30,7 @@ enum ErrorCode { * @deprecated. The new elasticsearch client doesn't wrap errors anymore. * @public * */ -export interface LegacyElasticsearchError extends Boom { +export interface LegacyElasticsearchError extends Boom.Boom { [code]?: string; } @@ -86,7 +86,7 @@ export class LegacyElasticsearchErrorHelpers { const decoratedError = decorate(error, ErrorCode.NOT_AUTHORIZED, 401, reason); const wwwAuthHeader = get(error, 'body.error.header[WWW-Authenticate]') as string; - decoratedError.output.headers['WWW-Authenticate'] = + (decoratedError.output.headers as { [key: string]: string })['WWW-Authenticate'] = wwwAuthHeader || 'Basic realm="Authorization Required"'; return decoratedError; diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index 71040598d34b12..cbb60480c4cf17 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -1214,7 +1214,7 @@ describe('timeout options', () => { router.get( { path: '/', - validate: { body: schema.any() }, + validate: { body: schema.maybe(schema.any()) }, }, (context, req, res) => { return res.ok({ @@ -1247,7 +1247,7 @@ describe('timeout options', () => { router.get( { path: '/', - validate: { body: schema.any() }, + validate: { body: schema.maybe(schema.any()) }, options: { timeout: { idleSocket: 12000 } }, }, (context, req, res) => { diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 43f5264ff22e31..42e89b66d9c519 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { Server } from '@hapi/hapi'; +import { Server, ServerRoute } from '@hapi/hapi'; import HapiStaticFiles from '@hapi/inert'; import url from 'url'; import uuid from 'uuid'; @@ -167,8 +167,6 @@ export class HttpServer { for (const router of this.registeredRouters) { for (const route of router.getRoutes()) { this.log.debug(`registering route handler for [${route.path}]`); - // Hapi does not allow payload validation to be specified for 'head' or 'get' requests - const validate = isSafeMethod(route.method) ? undefined : { payload: true }; const { authRequired, tags, body = {}, timeout } = route.options; const { accepts: allow, maxBytes, output, parse } = body; @@ -176,57 +174,45 @@ export class HttpServer { xsrfRequired: route.options.xsrfRequired ?? !isSafeMethod(route.method), }; - // To work around https://github.com/hapijs/hapi/issues/4122 until v20, set the socket - // timeout on the route to a fake timeout only when the payload timeout is specified. - // Within the onPreAuth lifecycle of the route itself, we'll override the timeout with the - // real socket timeout. - const fakeSocketTimeout = timeout?.payload ? timeout.payload + 1 : undefined; - - this.server.route({ + const routeOpts: ServerRoute = { handler: route.handler, method: route.method, path: route.path, options: { auth: this.getAuthOption(authRequired), app: kibanaRouteOptions, - ext: { - onPreAuth: { - method: (request, h) => { - // At this point, the socket timeout has only been set to work-around the HapiJS bug. - // We need to either set the real per-route timeout or use the default idle socket timeout - if (timeout?.idleSocket) { - request.raw.req.socket.setTimeout(timeout.idleSocket); - } else if (fakeSocketTimeout) { - // NodeJS uses a socket timeout of `0` to denote "no timeout" - request.raw.req.socket.setTimeout(this.config!.socketTimeout ?? 0); - } - - return h.continue; - }, - }, - }, tags: tags ? Array.from(tags) : undefined, - // TODO: This 'validate' section can be removed once the legacy platform is completely removed. - // We are telling Hapi that NP routes can accept any payload, so that it can bypass the default - // validation applied in ./http_tools#getServerOptions - // (All NP routes are already required to specify their own validation in order to access the payload) - validate, - payload: [allow, maxBytes, output, parse, timeout?.payload].some( - (v) => typeof v !== 'undefined' - ) + // @ts-expect-error Types are outdated and doesn't allow `payload.multipart` to be `true` + payload: [allow, maxBytes, output, parse, timeout?.payload].some((x) => x !== undefined) ? { allow, maxBytes, output, parse, timeout: timeout?.payload, + multipart: true, } : undefined, timeout: { - socket: fakeSocketTimeout, + socket: timeout?.idleSocket ?? this.config!.socketTimeout, }, }, - }); + }; + + // Hapi does not allow payload validation to be specified for 'head' or 'get' requests + if (!isSafeMethod(route.method)) { + // TODO: This 'validate' section can be removed once the legacy platform is completely removed. + // We are telling Hapi that NP routes can accept any payload, so that it can bypass the default + // validation applied in ./http_tools#getServerOptions + // (All NP routes are already required to specify their own validation in order to access the payload) + // TODO: Move the setting of the validate option back up to being set at `routeOpts` creation-time once + // https://github.com/hapijs/hoek/pull/365 is merged and released in @hapi/hoek v9.1.1. At that point I + // imagine the ts-error below will go away as well. + // @ts-expect-error "Property 'validate' does not exist on type 'RouteOptions'" <-- ehh?!? yes it does! + routeOpts.options!.validate = { payload: true }; + } + + this.server.route(routeOpts); } } diff --git a/src/core/server/http/lifecycle/on_pre_response.ts b/src/core/server/http/lifecycle/on_pre_response.ts index 42179374ec6727..9efcf46148e1fc 100644 --- a/src/core/server/http/lifecycle/on_pre_response.ts +++ b/src/core/server/http/lifecycle/on_pre_response.ts @@ -142,7 +142,11 @@ export function adoptToHapiOnPreResponseFormat(fn: OnPreResponseHandler, log: Lo if (preResponseResult.isNext(result)) { if (result.headers) { if (isBoom(response)) { - findHeadersIntersection(response.output.headers, result.headers, log); + findHeadersIntersection( + response.output.headers as { [key: string]: string }, + result.headers, + log + ); // hapi wraps all error response in Boom object internally response.output.headers = { ...response.output.headers, @@ -157,7 +161,7 @@ export function adoptToHapiOnPreResponseFormat(fn: OnPreResponseHandler, log: Lo const overriddenResponse = responseToolkit.response(result.body).code(statusCode); const originalHeaders = isBoom(response) ? response.output.headers : response.headers; - setHeaders(overriddenResponse, originalHeaders); + setHeaders(overriddenResponse, originalHeaders as { [key: string]: string }); if (result.headers) { setHeaders(overriddenResponse, result.headers); } @@ -178,8 +182,8 @@ export function adoptToHapiOnPreResponseFormat(fn: OnPreResponseHandler, log: Lo }; } -function isBoom(response: any): response is Boom { - return response instanceof Boom; +function isBoom(response: any): response is Boom.Boom { + return response instanceof Boom.Boom; } function setHeaders(response: ResponseObject, headers: ResponseHeaders) { diff --git a/src/core/server/http/router/error_wrapper.ts b/src/core/server/http/router/error_wrapper.ts index 5a4b7e9f775823..7d141e81ddf367 100644 --- a/src/core/server/http/router/error_wrapper.ts +++ b/src/core/server/http/router/error_wrapper.ts @@ -29,7 +29,7 @@ export const wrapErrors: RequestHandlerWrapper = (handler) => { return response.customError({ body: e.output.payload, statusCode: e.output.statusCode, - headers: e.output.headers, + headers: e.output.headers as { [key: string]: string }, }); } throw e; diff --git a/src/core/server/http/router/response_adapter.ts b/src/core/server/http/router/response_adapter.ts index 63acd2207ac3aa..d80c21bde8de8e 100644 --- a/src/core/server/http/router/response_adapter.ts +++ b/src/core/server/http/router/response_adapter.ts @@ -56,7 +56,7 @@ export class HapiResponseAdapter { } public toInternalError() { - const error = new Boom('', { + const error = new Boom.Boom('', { statusCode: 500, }); @@ -129,7 +129,7 @@ export class HapiResponseAdapter { } // we use for BWC with Boom payload for error responses - {error: string, message: string, statusCode: string} - const error = new Boom('', { + const error = new Boom.Boom('', { statusCode: kibanaResponse.status, }); @@ -142,8 +142,7 @@ export class HapiResponseAdapter { const headers = kibanaResponse.options.headers; if (headers) { - // Hapi typings for header accept only strings, although string[] is a valid value - error.output.headers = headers as any; + error.output.headers = headers; } return error; diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index b1e092ba5786ae..ebc41a793f3b38 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -44,7 +44,10 @@ interface RouterRoute { method: RouteMethod; path: string; options: RouteConfigOptions; - handler: (req: Request, responseToolkit: ResponseToolkit) => Promise>; + handler: ( + req: Request, + responseToolkit: ResponseToolkit + ) => Promise>; } /** diff --git a/src/core/server/saved_objects/service/lib/errors.ts b/src/core/server/saved_objects/service/lib/errors.ts index e8836dbd8f7a18..c6c8eee003e4ed 100644 --- a/src/core/server/saved_objects/service/lib/errors.ts +++ b/src/core/server/saved_objects/service/lib/errors.ts @@ -44,7 +44,7 @@ const CODE_GENERAL_ERROR = 'SavedObjectsClient/generalError'; const code = Symbol('SavedObjectsClientErrorCode'); -export interface DecoratedError extends Boom { +export interface DecoratedError extends Boom.Boom { [code]?: string; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 5f07a4b5230560..cef5f33726ed56 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1541,7 +1541,7 @@ export type LegacyElasticsearchClientConfig = Pick; const allowedList: CircularDepList = new Set([ - 'src/plugins/charts -> src/plugins/expressions', + 'src/plugins/charts -> src/plugins/discover', 'src/plugins/charts -> src/plugins/vis_default_editor', - 'src/plugins/data -> src/plugins/embeddable', - 'src/plugins/data -> src/plugins/expressions', - 'src/plugins/data -> src/plugins/ui_actions', - 'src/plugins/embeddable -> src/plugins/ui_actions', - 'src/plugins/expressions -> src/plugins/visualizations', 'src/plugins/vis_default_editor -> src/plugins/visualizations', 'src/plugins/vis_default_editor -> src/plugins/visualize', 'src/plugins/visualizations -> src/plugins/visualize', diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index 8eff48251b371e..845d64c16500df 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -45,6 +45,7 @@ import { removeQueryParam } from '../services/kibana_utils'; import { IndexPattern } from '../services/data'; import { EmbeddableRenderer } from '../services/embeddable'; import { DashboardContainerInput } from '.'; +import { leaveConfirmStrings } from '../dashboard_strings'; export interface DashboardAppProps { history: History; @@ -64,8 +65,9 @@ export function DashboardApp({ core, onAppLeave, uiSettings, - indexPatterns: indexPatternService, + embeddable, dashboardCapabilities, + indexPatterns: indexPatternService, } = useKibana().services; const [lastReloadTime, setLastReloadTime] = useState(0); @@ -196,9 +198,14 @@ export function DashboardApp({ return; } onAppLeave((actions) => { - if (dashboardStateManager?.getIsDirty()) { - // TODO: Finish App leave handler with overrides when redirecting to an editor. - // return actions.confirm(leaveConfirmStrings.leaveSubtitle, leaveConfirmStrings.leaveTitle); + if ( + dashboardStateManager?.getIsDirty() && + !embeddable.getStateTransfer().isTransferInProgress + ) { + return actions.confirm( + leaveConfirmStrings.getLeaveSubtitle(), + leaveConfirmStrings.getLeaveTitle() + ); } return actions.default(); }); @@ -206,7 +213,7 @@ export function DashboardApp({ // reset on app leave handler so leaving from the listing page doesn't trigger a confirmation onAppLeave((actions) => actions.default()); }; - }, [dashboardStateManager, dashboardContainer, onAppLeave]); + }, [dashboardStateManager, dashboardContainer, onAppLeave, embeddable]); // Refresh the dashboard container when lastReloadTime changes useEffect(() => { diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx index 937e6737d27168..915f245fbcd191 100644 --- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx @@ -27,6 +27,7 @@ import { useKibana } from '../../services/kibana_react'; import { IndexPattern, SavedQuery, TimefilterContract } from '../../services/data'; import { EmbeddableFactoryNotFoundError, + EmbeddableInput, isErrorEmbeddable, openAddPanelFlyout, ViewMode, @@ -135,10 +136,7 @@ export function DashboardTopNav({ if (!factory) { throw new EmbeddableFactoryNotFoundError(type); } - const explicitInput = await factory.getExplicitInput(); - if (dashboardContainer) { - await dashboardContainer.addNewEmbeddable(type, explicitInput); - } + await factory.create({} as EmbeddableInput, dashboardContainer); }, [dashboardContainer, embeddable]); const onChangeViewMode = useCallback( diff --git a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap index 19ec286307a09d..76de2b2662bb02 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap @@ -2,6 +2,7 @@ exports[`IndexPattern toSpec should match snapshot 1`] = ` Object { + "allowNoIndex": false, "fieldAttrs": Object {}, "fieldFormats": Object {}, "fields": Object { diff --git a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap index c020e7595c5657..bad74430b89668 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap @@ -2,6 +2,7 @@ exports[`IndexPatterns savedObjectToSpec 1`] = ` Object { + "allowNoIndex": undefined, "fieldAttrs": Object {}, "fieldFormats": Object { "field": Object {}, diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index 4c89cbeb446a07..590ff872f3bf93 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -74,6 +74,10 @@ export class IndexPattern implements IIndexPattern { private fieldFormats: FieldFormatsStartCommon; // make private once manual field refresh is removed public fieldAttrs: FieldAttrs; + /** + * prevents errors when index pattern exists before indices + */ + public readonly allowNoIndex: boolean = false; constructor({ spec = {}, @@ -110,6 +114,7 @@ export class IndexPattern implements IIndexPattern { this.typeMeta = spec.typeMeta; this.fieldAttrs = spec.fieldAttrs || {}; this.intervalName = spec.intervalName; + this.allowNoIndex = spec.allowNoIndex || false; } /** @@ -204,6 +209,7 @@ export class IndexPattern implements IIndexPattern { fieldFormats: this.fieldFormatMap, fieldAttrs: this.fieldAttrs, intervalName: this.intervalName, + allowNoIndex: this.allowNoIndex, }; } @@ -309,6 +315,7 @@ export class IndexPattern implements IIndexPattern { fieldFormatMap, type: this.type, typeMeta: this.typeMeta ? JSON.stringify(this.typeMeta) : undefined, + allowNoIndex: this.allowNoIndex ? this.allowNoIndex : undefined, }; } diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts index 2a203b57d201b2..3d32742c168ad7 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts @@ -114,6 +114,21 @@ describe('IndexPatterns', () => { SOClientGetDelay = 0; }); + test('allowNoIndex flag preserves existing fields when index is missing', async () => { + const id = '2'; + setDocsourcePayload(id, { + id: 'foo', + version: 'foo', + attributes: { + title: 'something', + allowNoIndex: true, + fields: '[{"name":"field"}]', + }, + }); + + expect((await indexPatterns.get(id)).fields.length).toBe(1); + }); + test('savedObjectCache pre-fetches only title', async () => { expect(await indexPatterns.getIds()).toEqual(['id']); expect(savedObjectsClient.find).toHaveBeenCalledWith({ diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts index 0235f748ec1e04..3333dba36fe69c 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts @@ -222,6 +222,7 @@ export class IndexPatternsService { metaFields, type: options.type, rollupIndex: options.rollupIndex, + allowNoIndex: options.allowNoIndex, }); }; @@ -281,10 +282,21 @@ export class IndexPatternsService { options: GetFieldsOptions, fieldAttrs: FieldAttrs = {} ) => { - const scriptedFields = Object.values(fields).filter((field) => field.scripted); + const fieldsAsArr = Object.values(fields); + const scriptedFields = fieldsAsArr.filter((field) => field.scripted); try { + let updatedFieldList: FieldSpec[]; const newFields = (await this.getFieldsForWildcard(options)) as FieldSpec[]; - return this.fieldArrayToMap([...newFields, ...scriptedFields], fieldAttrs); + + // If allowNoIndex, only update field list if field caps finds fields. To support + // beats creating index pattern and dashboard before docs + if (!options.allowNoIndex || (newFields && newFields.length > 5)) { + updatedFieldList = [...newFields, ...scriptedFields]; + } else { + updatedFieldList = fieldsAsArr; + } + + return this.fieldArrayToMap(updatedFieldList, fieldAttrs); } catch (err) { if (err instanceof IndexPatternMissingIndices) { this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' }); @@ -334,6 +346,7 @@ export class IndexPatternsService { typeMeta, type, fieldAttrs, + allowNoIndex, }, } = savedObject; @@ -355,6 +368,7 @@ export class IndexPatternsService { type, fieldFormats: parsedFieldFormatMap, fieldAttrs: parsedFieldAttrs, + allowNoIndex, }; }; @@ -384,6 +398,7 @@ export class IndexPatternsService { metaFields: await this.config.get(UI_SETTINGS.META_FIELDS), type, rollupIndex: typeMeta?.params?.rollup_index, + allowNoIndex: spec.allowNoIndex, }, spec.fieldAttrs ); diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index 8d9b29175162e8..12496b07d34820 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -49,6 +49,10 @@ export interface IndexPatternAttributes { sourceFilters?: string; fieldFormatMap?: string; fieldAttrs?: string; + /** + * prevents errors when index pattern exists before indices + */ + allowNoIndex?: boolean; } export interface FieldAttrs { @@ -101,6 +105,7 @@ export interface GetFieldsOptions { lookBack?: boolean; metaFields?: string[]; rollupIndex?: string; + allowNoIndex?: boolean; } export interface GetFieldsOptionsTimePattern { @@ -193,6 +198,7 @@ export interface IndexPatternSpec { type?: string; fieldFormats?: Record; fieldAttrs?: FieldAttrs; + allowNoIndex?: boolean; } export interface SourceFilter { diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts index 78d169e8529c5a..9d0c63a8dc7aa4 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts @@ -150,7 +150,8 @@ describe('esaggs expression function - public', () => { }); }); - test('calls agg.postFlightRequest if it exiests', async () => { + test('calls agg.postFlightRequest if it exiests and agg is enabled', async () => { + mockParams.aggs.aggs[0].enabled = true; await handleRequest(mockParams); expect(mockParams.aggs.aggs[0].type.postFlightRequest).toHaveBeenCalledTimes(1); @@ -160,6 +161,12 @@ describe('esaggs expression function - public', () => { expect(async () => await handleRequest(mockParams)).not.toThrowError(); }); + test('should skip agg.postFlightRequest call if the agg is disabled', async () => { + mockParams.aggs.aggs[0].enabled = false; + await handleRequest(mockParams); + expect(mockParams.aggs.aggs[0].type.postFlightRequest).toHaveBeenCalledTimes(0); + }); + test('tabifies response data', async () => { await handleRequest(mockParams); expect(tabifyAggResponse).toHaveBeenCalledWith( diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts index e4385526ee6e8f..b773aad67c3f89 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts @@ -170,7 +170,7 @@ export const handleRequest = async ({ // response data incorrectly in the inspector. let response = (searchSource as any).rawResponse; for (const agg of aggs.aggs) { - if (typeof agg.type.postFlightRequest === 'function') { + if (agg.enabled && typeof agg.type.postFlightRequest === 'function') { response = await agg.type.postFlightRequest( response, aggs, diff --git a/src/plugins/data/public/actions/apply_filter_action.ts b/src/plugins/data/public/actions/apply_filter_action.ts index 944da72bd11d1d..84ce5b03826247 100644 --- a/src/plugins/data/public/actions/apply_filter_action.ts +++ b/src/plugins/data/public/actions/apply_filter_action.ts @@ -22,7 +22,6 @@ import { toMountPoint } from '../../../kibana_react/public'; import { ActionByType, createAction, IncompatibleActionError } from '../../../ui_actions/public'; import { getOverlays, getIndexPatterns } from '../services'; import { applyFiltersPopover } from '../ui/apply_filters'; -import type { IEmbeddable } from '../../../embeddable/public'; import { Filter, FilterManager, TimefilterContract, esFilters } from '..'; export const ACTION_GLOBAL_APPLY_FILTER = 'ACTION_GLOBAL_APPLY_FILTER'; @@ -30,7 +29,9 @@ export const ACTION_GLOBAL_APPLY_FILTER = 'ACTION_GLOBAL_APPLY_FILTER'; export interface ApplyGlobalFilterActionContext { filters: Filter[]; timeFieldName?: string; - embeddable?: IEmbeddable; + // Need to make this unknown to prevent circular dependencies. + // Apps using this property will need to cast to `IEmbeddable`. + embeddable?: unknown; } async function isCompatible(context: ApplyGlobalFilterActionContext) { diff --git a/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts index 2d7aeff79a689a..2b0911b72abd58 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts @@ -19,12 +19,20 @@ import { last } from 'lodash'; import moment from 'moment'; +import { Datatable } from 'src/plugins/expressions'; import { esFilters, IFieldType, RangeFilterParams } from '../../../public'; import { getIndexPatterns, getSearchService } from '../../../public/services'; -import { RangeSelectContext } from '../../../../embeddable/public'; import { AggConfigSerialized } from '../../../common/search/aggs'; -export async function createFiltersFromRangeSelectAction(event: RangeSelectContext['data']) { +/** @internal */ +export interface RangeSelectDataContext { + table: Datatable; + column: number; + range: number[]; + timeFieldName?: string; +} + +export async function createFiltersFromRangeSelectAction(event: RangeSelectDataContext) { const column: Record = event.table.columns[event.column]; if (!column || !column.meta) { diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts index 23d2ab080d75ef..04801a5ee1ceab 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts @@ -25,8 +25,10 @@ import { } from '../../../public'; import { dataPluginMock } from '../../../public/mocks'; import { setIndexPatterns, setSearchService } from '../../../public/services'; -import { createFiltersFromValueClickAction } from './create_filters_from_value_click'; -import { ValueClickContext } from '../../../../embeddable/public'; +import { + createFiltersFromValueClickAction, + ValueClickDataContext, +} from './create_filters_from_value_click'; const mockField = { name: 'bytes', @@ -34,7 +36,7 @@ const mockField = { }; describe('createFiltersFromValueClick', () => { - let dataPoints: ValueClickContext['data']['data']; + let dataPoints: ValueClickDataContext['data']; beforeEach(() => { dataPoints = [ diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts index ce7ecf434056a6..30fef7e3a7c66f 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts @@ -20,9 +20,20 @@ import { Datatable } from '../../../../../plugins/expressions/public'; import { esFilters, Filter } from '../../../public'; import { getIndexPatterns, getSearchService } from '../../../public/services'; -import { ValueClickContext } from '../../../../embeddable/public'; import { AggConfigSerialized } from '../../../common/search/aggs'; +/** @internal */ +export interface ValueClickDataContext { + data: Array<{ + table: Pick; + column: number; + row: number; + value: any; + }>; + timeFieldName?: string; + negate?: boolean; +} + /** * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter * terms based on a specific cell in the tabified data. @@ -120,7 +131,7 @@ const createFilter = async ( export const createFiltersFromValueClickAction = async ({ data, negate, -}: ValueClickContext['data']) => { +}: ValueClickDataContext) => { const filters: Filter[] = []; await Promise.all( diff --git a/src/plugins/data/public/actions/select_range_action.ts b/src/plugins/data/public/actions/select_range_action.ts index 1781da980dc30d..3b84523d782f60 100644 --- a/src/plugins/data/public/actions/select_range_action.ts +++ b/src/plugins/data/public/actions/select_range_action.ts @@ -17,16 +17,22 @@ * under the License. */ -import { - ActionByType, - APPLY_FILTER_TRIGGER, - createAction, - UiActionsStart, -} from '../../../../plugins/ui_actions/public'; +import { Datatable } from 'src/plugins/expressions/public'; +import { ActionByType, createAction, UiActionsStart } from '../../../../plugins/ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../triggers'; import { createFiltersFromRangeSelectAction } from './filters/create_filters_from_range_select'; -import type { RangeSelectContext } from '../../../embeddable/public'; -export type SelectRangeActionContext = RangeSelectContext; +export interface SelectRangeActionContext { + // Need to make this unknown to prevent circular dependencies. + // Apps using this property will need to cast to `IEmbeddable`. + embeddable?: unknown; + data: { + table: Datatable; + column: number; + range: number[]; + timeFieldName?: string; + }; +} export const ACTION_SELECT_RANGE = 'ACTION_SELECT_RANGE'; diff --git a/src/plugins/data/public/actions/value_click_action.ts b/src/plugins/data/public/actions/value_click_action.ts index 81e62380eacfb5..8f207e94e8fbe9 100644 --- a/src/plugins/data/public/actions/value_click_action.ts +++ b/src/plugins/data/public/actions/value_click_action.ts @@ -17,19 +17,31 @@ * under the License. */ -import { - ActionByType, - APPLY_FILTER_TRIGGER, - createAction, - UiActionsStart, -} from '../../../../plugins/ui_actions/public'; +import { Datatable } from 'src/plugins/expressions/public'; +import { ActionByType, createAction, UiActionsStart } from '../../../../plugins/ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../triggers'; import { createFiltersFromValueClickAction } from './filters/create_filters_from_value_click'; import type { Filter } from '../../common/es_query/filters'; -import type { ValueClickContext } from '../../../embeddable/public'; export type ValueClickActionContext = ValueClickContext; export const ACTION_VALUE_CLICK = 'ACTION_VALUE_CLICK'; +export interface ValueClickContext { + // Need to make this unknown to prevent circular dependencies. + // Apps using this property will need to cast to `IEmbeddable`. + embeddable?: unknown; + data: { + data: Array<{ + table: Pick; + column: number; + row: number; + value: any; + }>; + timeFieldName?: string; + negate?: boolean; + }; +} + export function createValueClickAction( getStartServices: () => { uiActions: UiActionsStart } ): ActionByType { diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 3dda04d738c969..7b15e2576e704a 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -483,6 +483,7 @@ export { export { isTimeRange, isQuery, isFilter, isFilters } from '../common'; export { ACTION_GLOBAL_APPLY_FILTER, ApplyGlobalFilterActionContext } from './actions'; +export { APPLY_FILTER_TRIGGER } from './triggers'; /* * Plugin setup diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts index ca0f35d6612b2e..36a193a4f6f94b 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts @@ -64,12 +64,13 @@ export class IndexPatternsApiClient implements IIndexPatternsApiClient { }).then((resp: any) => resp.fields); } - getFieldsForWildcard({ pattern, metaFields, type, rollupIndex }: GetFieldsOptions) { + getFieldsForWildcard({ pattern, metaFields, type, rollupIndex, allowNoIndex }: GetFieldsOptions) { return this._request(this._getUrl(['_fields_for_wildcard']), { pattern, meta_fields: metaFields, type, rollup_index: rollupIndex, - }).then((resp: any) => resp.fields); + allow_no_index: allowNoIndex, + }).then((resp: any) => resp.fields || []); } } diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index eb3a053b78a2d3..c60a1efabf9871 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -48,11 +48,6 @@ import { setUiSettings, } from './services'; import { createSearchBar } from './ui/search_bar/create_search_bar'; -import { - SELECT_RANGE_TRIGGER, - VALUE_CLICK_TRIGGER, - APPLY_FILTER_TRIGGER, -} from '../../ui_actions/public'; import { ACTION_GLOBAL_APPLY_FILTER, createFilterAction, @@ -66,13 +61,18 @@ import { createValueClickAction, createSelectRangeAction, } from './actions'; - +import { APPLY_FILTER_TRIGGER, applyFilterTrigger } from './triggers'; import { SavedObjectsClientPublicToCommon } from './index_patterns'; import { getIndexPatternLoad } from './index_patterns/expressions'; import { UsageCollectionSetup } from '../../usage_collection/public'; import { getTableViewDescription } from './utils/table_inspector_view'; +import { TriggerId } from '../../ui_actions/public'; declare module '../../ui_actions/public' { + export interface TriggerContextMapping { + [APPLY_FILTER_TRIGGER]: ApplyGlobalFilterActionContext; + } + export interface ActionContextMapping { [ACTION_GLOBAL_APPLY_FILTER]: ApplyGlobalFilterActionContext; [ACTION_SELECT_RANGE]: SelectRangeActionContext; @@ -118,19 +118,21 @@ export class DataPublicPlugin storage: this.storage, }); + uiActions.registerTrigger(applyFilterTrigger); + uiActions.registerAction( createFilterAction(queryService.filterManager, queryService.timefilter.timefilter) ); uiActions.addTriggerAction( - SELECT_RANGE_TRIGGER, + 'SELECT_RANGE_TRIGGER' as TriggerId, createSelectRangeAction(() => ({ uiActions: startServices().plugins.uiActions, })) ); uiActions.addTriggerAction( - VALUE_CLICK_TRIGGER, + 'VALUE_CLICK_TRIGGER' as TriggerId, createValueClickAction(() => ({ uiActions: startServices().plugins.uiActions, })) diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index e5df6d860b4041..3493844a71ac15 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -464,14 +464,17 @@ export type AggsStart = Assign; +// Warning: (ae-missing-release-tag) "APPLY_FILTER_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const APPLY_FILTER_TRIGGER = "FILTER_TRIGGER"; + // Warning: (ae-missing-release-tag) "ApplyGlobalFilterActionContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export interface ApplyGlobalFilterActionContext { - // Warning: (ae-forgotten-export) The symbol "IEmbeddable" needs to be exported by the entry point index.d.ts - // // (undocumented) - embeddable?: IEmbeddable; + embeddable?: unknown; // (undocumented) filters: Filter[]; // (undocumented) @@ -1253,6 +1256,7 @@ export class IndexPattern implements IIndexPattern { // Warning: (ae-forgotten-export) The symbol "IndexPatternDeps" needs to be exported by the entry point index.d.ts constructor({ spec, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); addScriptedField(name: string, script: string, fieldType?: string): Promise; + readonly allowNoIndex: boolean; // (undocumented) readonly deleteFieldFormat: (fieldName: string) => void; // Warning: (ae-forgotten-export) The symbol "FieldAttrs" needs to be exported by the entry point index.d.ts @@ -1293,6 +1297,7 @@ export class IndexPattern implements IIndexPattern { fieldFormatMap: string | undefined; type: string | undefined; typeMeta: string | undefined; + allowNoIndex: true | undefined; }; // (undocumented) getComputedFields(): { @@ -1385,6 +1390,7 @@ export type IndexPatternAggRestrictions = Record, 'isLo // // @public (undocumented) export interface IndexPatternSpec { + // (undocumented) + allowNoIndex?: boolean; // (undocumented) fieldAttrs?: FieldAttrs; // (undocumented) @@ -2561,7 +2569,7 @@ export const UI_SETTINGS: { // src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:64:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:128:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:133:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/aggs/types.ts:150:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/search_source/search_source.ts:197:7 - (ae-forgotten-export) The symbol "SearchFieldValue" needs to be exported by the entry point index.d.ts // src/plugins/data/public/field_formats/field_formats_service.ts:67:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/ui_actions/public/triggers/apply_filter_trigger.ts b/src/plugins/data/public/triggers/apply_filter_trigger.ts similarity index 85% rename from src/plugins/ui_actions/public/triggers/apply_filter_trigger.ts rename to src/plugins/data/public/triggers/apply_filter_trigger.ts index aa54706476a8f5..816c1737608daa 100644 --- a/src/plugins/ui_actions/public/triggers/apply_filter_trigger.ts +++ b/src/plugins/data/public/triggers/apply_filter_trigger.ts @@ -18,15 +18,15 @@ */ import { i18n } from '@kbn/i18n'; -import { Trigger } from '.'; +import { Trigger } from '../../../ui_actions/public'; export const APPLY_FILTER_TRIGGER = 'FILTER_TRIGGER'; export const applyFilterTrigger: Trigger<'FILTER_TRIGGER'> = { id: APPLY_FILTER_TRIGGER, - title: i18n.translate('uiActions.triggers.applyFilterTitle', { + title: i18n.translate('data.triggers.applyFilterTitle', { defaultMessage: 'Apply filter', }), - description: i18n.translate('uiActions.triggers.applyFilterDescription', { + description: i18n.translate('data.triggers.applyFilterDescription', { defaultMessage: 'When kibana filter is applied. Could be a single value or a range filter.', }), }; diff --git a/src/plugins/ui_actions/public/triggers/value_click_trigger.ts b/src/plugins/data/public/triggers/index.ts similarity index 62% rename from src/plugins/ui_actions/public/triggers/value_click_trigger.ts rename to src/plugins/data/public/triggers/index.ts index f1aff6322522ae..36a38ae76bc0e9 100644 --- a/src/plugins/ui_actions/public/triggers/value_click_trigger.ts +++ b/src/plugins/data/public/triggers/index.ts @@ -17,16 +17,4 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -import { Trigger } from '.'; - -export const VALUE_CLICK_TRIGGER = 'VALUE_CLICK_TRIGGER'; -export const valueClickTrigger: Trigger<'VALUE_CLICK_TRIGGER'> = { - id: VALUE_CLICK_TRIGGER, - title: i18n.translate('uiActions.triggers.valueClickTitle', { - defaultMessage: 'Single click', - }), - description: i18n.translate('uiActions.triggers.valueClickDescription', { - defaultMessage: 'A data point click on the visualization', - }), -}; +export * from './apply_filter_trigger'; diff --git a/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx b/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx index f4d1a8988da788..f842568859fc24 100644 --- a/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx +++ b/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx @@ -38,7 +38,7 @@ import { DataViewRow, DataViewColumn } from '../types'; import { IUiSettingsClient } from '../../../../../../core/public'; import { Datatable, DatatableColumn } from '../../../../../expressions/public'; import { FieldFormatsStart } from '../../../field_formats'; -import { UiActionsStart } from '../../../../../ui_actions/public'; +import { TriggerId, UiActionsStart } from '../../../../../ui_actions/public'; interface DataTableFormatState { columns: DataViewColumn[]; @@ -112,7 +112,7 @@ export class DataTableFormat extends Component { const value = table.rows[rowIndex][column.id]; const eventData = { table, column: columnIndex, row: rowIndex, value }; - uiActions.executeTriggerActions('VALUE_CLICK_TRIGGER', { + uiActions.executeTriggerActions('VALUE_CLICK_TRIGGER' as TriggerId, { data: { data: [eventData] }, }); }} @@ -145,7 +145,7 @@ export class DataTableFormat extends Component { const value = table.rows[rowIndex][column.id]; const eventData = { table, column: columnIndex, row: rowIndex, value }; - uiActions.executeTriggerActions('VALUE_CLICK_TRIGGER', { + uiActions.executeTriggerActions('VALUE_CLICK_TRIGGER' as TriggerId, { data: { data: [eventData], negate: true }, }); }} diff --git a/src/plugins/data/server/index_patterns/index_patterns_api_client.ts b/src/plugins/data/server/index_patterns/index_patterns_api_client.ts index 21a3bf6e73e611..9023044184df31 100644 --- a/src/plugins/data/server/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/server/index_patterns/index_patterns_api_client.ts @@ -30,8 +30,14 @@ export class IndexPatternsApiServer implements IIndexPatternsApiClient { constructor(elasticsearchClient: ElasticsearchClient) { this.esClient = elasticsearchClient; } - async getFieldsForWildcard({ pattern, metaFields, type, rollupIndex }: GetFieldsOptions) { - const indexPatterns = new IndexPatternsFetcher(this.esClient); + async getFieldsForWildcard({ + pattern, + metaFields, + type, + rollupIndex, + allowNoIndex, + }: GetFieldsOptions) { + const indexPatterns = new IndexPatternsFetcher(this.esClient, allowNoIndex); return await indexPatterns.getFieldsForWildcard({ pattern, metaFields, diff --git a/src/plugins/data/server/index_patterns/routes.ts b/src/plugins/data/server/index_patterns/routes.ts index e9dbc2e972c688..f0b51e456337f5 100644 --- a/src/plugins/data/server/index_patterns/routes.ts +++ b/src/plugins/data/server/index_patterns/routes.ts @@ -75,13 +75,20 @@ export function registerRoutes( }), type: schema.maybe(schema.string()), rollup_index: schema.maybe(schema.string()), + allow_no_index: schema.maybe(schema.boolean()), }), }, }, async (context, request, response) => { const { asCurrentUser } = context.core.elasticsearch.client; const indexPatterns = new IndexPatternsFetcher(asCurrentUser); - const { pattern, meta_fields: metaFields, type, rollup_index: rollupIndex } = request.query; + const { + pattern, + meta_fields: metaFields, + type, + rollup_index: rollupIndex, + allow_no_index: allowNoIndex, + } = request.query; let parsedFields: string[] = []; try { @@ -96,6 +103,9 @@ export function registerRoutes( metaFields: parsedFields, type, rollupIndex, + fieldCapsOptions: { + allow_no_indices: allowNoIndex || false, + }, }); return response.ok({ diff --git a/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts b/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts index 57a745b19748da..1163fd2dc9953e 100644 --- a/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts +++ b/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts @@ -50,6 +50,7 @@ const indexPatternSpecSchema = schema.object({ }) ) ), + allowNoIndex: schema.maybe(schema.boolean()), }); export const registerCreateIndexPatternRoute = ( diff --git a/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts b/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts index 10567544af6ea2..8bd59e47730fd4 100644 --- a/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts +++ b/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts @@ -38,6 +38,7 @@ const indexPatternUpdateSchema = schema.object({ ), fieldFormats: schema.maybe(schema.recordOf(schema.string(), serializedFieldFormatSchema)), fields: schema.maybe(schema.recordOf(schema.string(), fieldSpecSchema)), + allowNoIndex: schema.maybe(schema.boolean()), }); export const registerUpdateIndexPatternRoute = ( diff --git a/src/plugins/data/server/saved_objects/index_pattern_migrations.test.ts b/src/plugins/data/server/saved_objects/index_pattern_migrations.test.ts index b1410e24986676..3b223e6fdb9b21 100644 --- a/src/plugins/data/server/saved_objects/index_pattern_migrations.test.ts +++ b/src/plugins/data/server/saved_objects/index_pattern_migrations.test.ts @@ -94,4 +94,55 @@ Object { expect(migrationFn(input, savedObjectMigrationContext)).toEqual(expected); }); }); + + describe('7.11.0', () => { + const migrationFn = indexPatternSavedObjectTypeMigrations['7.11.0']; + + test('should set allowNoIndex', () => { + const input = { + type: 'index-pattern', + id: 'logs-*', + attributes: {}, + }; + const expected = { + type: 'index-pattern', + id: 'logs-*', + attributes: { + allowNoIndex: true, + }, + }; + + expect(migrationFn(input, savedObjectMigrationContext)).toEqual(expected); + + const input2 = { + type: 'index-pattern', + id: 'metrics-*', + attributes: {}, + }; + const expected2 = { + type: 'index-pattern', + id: 'metrics-*', + attributes: { + allowNoIndex: true, + }, + }; + + expect(migrationFn(input2, savedObjectMigrationContext)).toEqual(expected2); + + const input3 = { + type: 'index-pattern', + id: 'xxx', + attributes: {}, + }; + const expected3 = { + type: 'index-pattern', + id: 'xxx', + attributes: { + allowNoIndex: undefined, + }, + }; + + expect(migrationFn(input3, savedObjectMigrationContext)).toEqual(expected3); + }); + }); }); diff --git a/src/plugins/data/server/saved_objects/index_pattern_migrations.ts b/src/plugins/data/server/saved_objects/index_pattern_migrations.ts index 768041a376ad1d..4650aeefba0565 100644 --- a/src/plugins/data/server/saved_objects/index_pattern_migrations.ts +++ b/src/plugins/data/server/saved_objects/index_pattern_migrations.ts @@ -54,7 +54,16 @@ const migrateSubTypeAndParentFieldProperties: SavedObjectMigrationFn = }; }; +const addAllowNoIndex: SavedObjectMigrationFn = (doc) => ({ + ...doc, + attributes: { + ...doc.attributes, + allowNoIndex: doc.id === 'logs-*' || doc.id === 'metrics-*' || undefined, + }, +}); + export const indexPatternSavedObjectTypeMigrations = { '6.5.0': flow(migrateAttributeTypeAndAttributeTypeMeta), '7.6.0': flow(migrateSubTypeAndParentFieldProperties), + '7.11.0': flow(addAllowNoIndex), }; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 4d24e6d1afd498..cd3527d5ad7ab2 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -689,6 +689,7 @@ export class IndexPattern implements IIndexPattern { // Warning: (ae-forgotten-export) The symbol "IndexPatternDeps" needs to be exported by the entry point index.d.ts constructor({ spec, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); addScriptedField(name: string, script: string, fieldType?: string): Promise; + readonly allowNoIndex: boolean; // (undocumented) readonly deleteFieldFormat: (fieldName: string) => void; // Warning: (ae-forgotten-export) The symbol "FieldAttrs" needs to be exported by the entry point index.d.ts @@ -731,6 +732,7 @@ export class IndexPattern implements IIndexPattern { fieldFormatMap: string | undefined; type: string | undefined; typeMeta: string | undefined; + allowNoIndex: true | undefined; }; // (undocumented) getComputedFields(): { @@ -819,6 +821,7 @@ export class IndexPattern implements IIndexPattern { // // @public (undocumented) export interface IndexPatternAttributes { + allowNoIndex?: boolean; // (undocumented) fieldAttrs?: string; // (undocumented) @@ -1115,7 +1118,7 @@ export class Plugin implements Plugin_2 void; search: ISearchSetup; fieldFormats: { - register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number; + register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number; }; }; // (undocumented) @@ -1124,7 +1127,7 @@ export class Plugin implements Plugin_2 Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -1388,7 +1391,7 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/common/es_query/filters/meta_filter.ts:54:3 - (ae-forgotten-export) The symbol "FilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:58:45 - (ae-forgotten-export) The symbol "IndexPatternFieldMap" needs to be exported by the entry point index.d.ts // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:64:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:128:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:133:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildCustomFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:57:23 - (ae-forgotten-export) The symbol "datatableToCSV" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 2c3b8fd9606a95..99497d61c716e8 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -65,11 +65,13 @@ import { MODIFY_COLUMNS_ON_SWITCH, SAMPLE_SIZE_SETTING, SEARCH_ON_PAGE_LOAD_SETTING, + SORT_DEFAULT_ORDER_SETTING, } from '../../../common'; import { loadIndexPattern, resolveIndexPattern } from '../helpers/resolve_index_pattern'; import { getTopNavLinks } from '../components/top_nav/get_top_nav_links'; import { updateSearchSource } from '../helpers/update_search_source'; import { calcFieldCounts } from '../helpers/calc_field_counts'; +import { getDefaultSort } from './doc_table/lib/get_default_sort'; const services = getServices(); @@ -410,9 +412,13 @@ function discoverController($element, $route, $scope, $timeout, Promise, uiCapab function getStateDefaults() { const query = $scope.searchSource.getField('query') || data.query.queryString.getDefaultQuery(); + const sort = getSortArray(savedSearch.sort, $scope.indexPattern); + return { query, - sort: getSortArray(savedSearch.sort, $scope.indexPattern), + sort: !sort.length + ? getDefaultSort($scope.indexPattern, config.get(SORT_DEFAULT_ORDER_SETTING, 'desc')) + : sort, columns: savedSearch.columns.length > 0 ? savedSearch.columns diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.test.ts b/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.test.ts new file mode 100644 index 00000000000000..9ad19653a6c12d --- /dev/null +++ b/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.test.ts @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { getDefaultSort } from './get_default_sort'; +// @ts-ignore +import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; +import { IndexPattern } from '../../../../kibana_services'; + +describe('getDefaultSort function', function () { + let indexPattern: IndexPattern; + beforeEach(() => { + indexPattern = FixturesStubbedLogstashIndexPatternProvider() as IndexPattern; + }); + test('should be a function', function () { + expect(typeof getDefaultSort === 'function').toBeTruthy(); + }); + + test('should return default sort for an index pattern with timeFieldName', function () { + expect(getDefaultSort(indexPattern, 'desc')).toEqual([['time', 'desc']]); + expect(getDefaultSort(indexPattern, 'asc')).toEqual([['time', 'asc']]); + }); + + test('should return default sort for an index pattern without timeFieldName', function () { + delete indexPattern.timeFieldName; + expect(getDefaultSort(indexPattern, 'desc')).toEqual([]); + expect(getDefaultSort(indexPattern, 'asc')).toEqual([]); + }); +}); diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.ts b/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.ts index 634e3cfec3a0bd..c1e4da0bab54d3 100644 --- a/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.ts +++ b/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.ts @@ -17,7 +17,6 @@ * under the License. */ import { IndexPattern } from '../../../../kibana_services'; -// @ts-ignore import { isSortable } from './get_sort'; import { SortOrder } from '../components/table_header/helpers'; @@ -26,12 +25,12 @@ import { SortOrder } from '../components/table_header/helpers'; * the default sort is returned depending of the index pattern */ export function getDefaultSort( - indexPattern: IndexPattern, + indexPattern: IndexPattern | undefined, defaultSortOrder: string = 'desc' ): SortOrder[] { - if (indexPattern.timeFieldName && isSortable(indexPattern.timeFieldName, indexPattern)) { + if (indexPattern?.timeFieldName && isSortable(indexPattern.timeFieldName, indexPattern)) { return [[indexPattern.timeFieldName, defaultSortOrder]]; } else { - return [['_score', defaultSortOrder]]; + return []; } } diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.test.ts b/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.test.ts new file mode 100644 index 00000000000000..1dbd31897d3077 --- /dev/null +++ b/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.test.ts @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { getSortForSearchSource } from './get_sort_for_search_source'; +// @ts-ignore +import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; +import { IndexPattern } from '../../../../kibana_services'; +import { SortOrder } from '../components/table_header/helpers'; + +describe('getSortForSearchSource function', function () { + let indexPattern: IndexPattern; + beforeEach(() => { + indexPattern = FixturesStubbedLogstashIndexPatternProvider() as IndexPattern; + }); + test('should be a function', function () { + expect(typeof getSortForSearchSource === 'function').toBeTruthy(); + }); + + test('should return an object to use for searchSource when columns are given', function () { + const cols = [['bytes', 'desc']] as SortOrder[]; + expect(getSortForSearchSource(cols, indexPattern)).toEqual([{ bytes: 'desc' }]); + expect(getSortForSearchSource(cols, indexPattern, 'asc')).toEqual([{ bytes: 'desc' }]); + delete indexPattern.timeFieldName; + expect(getSortForSearchSource(cols, indexPattern)).toEqual([{ bytes: 'desc' }]); + expect(getSortForSearchSource(cols, indexPattern, 'asc')).toEqual([{ bytes: 'desc' }]); + }); + + test('should return an object to use for searchSource when no columns are given', function () { + const cols = [] as SortOrder[]; + expect(getSortForSearchSource(cols, indexPattern)).toEqual([{ _doc: 'desc' }]); + expect(getSortForSearchSource(cols, indexPattern, 'asc')).toEqual([{ _doc: 'asc' }]); + delete indexPattern.timeFieldName; + expect(getSortForSearchSource(cols, indexPattern)).toEqual([{ _score: 'desc' }]); + expect(getSortForSearchSource(cols, indexPattern, 'asc')).toEqual([{ _score: 'asc' }]); + }); +}); diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.ts b/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.ts index 6721f7a03584cd..1244a0e229cdbd 100644 --- a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.ts +++ b/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.ts @@ -19,7 +19,6 @@ import { EsQuerySortValue, IndexPattern } from '../../../../kibana_services'; import { SortOrder } from '../components/table_header/helpers'; import { getSort } from './get_sort'; -import { getDefaultSort } from './get_default_sort'; /** * Prepares sort for search source, that's sending the request to ES @@ -33,10 +32,13 @@ export function getSortForSearchSource( indexPattern?: IndexPattern, defaultDirection: string = 'desc' ): EsQuerySortValue[] { - if (!sort || !indexPattern) { - return []; - } else if (Array.isArray(sort) && sort.length === 0) { - sort = getDefaultSort(indexPattern, defaultDirection); + if (!sort || !indexPattern || (Array.isArray(sort) && sort.length === 0)) { + if (indexPattern?.timeFieldName) { + // sorting by index order + return [{ _doc: defaultDirection } as EsQuerySortValue]; + } else { + return [{ _score: defaultDirection } as EsQuerySortValue]; + } } const { timeFieldName } = indexPattern; return getSort(sort, indexPattern).map((sortPair: Record) => { diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts index b143afd1988e68..d0c3907d312420 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts @@ -21,9 +21,10 @@ import angular from 'angular'; import _ from 'lodash'; import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; -import { UiActionsStart, APPLY_FILTER_TRIGGER } from '../../../../ui_actions/public'; +import { UiActionsStart } from '../../../../ui_actions/public'; import { RequestAdapter, Adapters } from '../../../../inspector/public'; import { + APPLY_FILTER_TRIGGER, esFilters, Filter, TimeRange, @@ -48,6 +49,7 @@ import { import { SEARCH_EMBEDDABLE_TYPE } from './constants'; import { SavedSearch } from '../..'; import { SAMPLE_SIZE_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../common'; +import { getDefaultSort } from '../angular/doc_table/lib/get_default_sort'; interface SearchScope extends ng.IScope { columns?: string[]; @@ -200,6 +202,13 @@ export class SearchEmbeddable const { searchSource } = this.savedSearch; const indexPattern = (searchScope.indexPattern = searchSource.getField('index'))!; + if (!this.savedSearch.sort || !this.savedSearch.sort.length) { + this.savedSearch.sort = getDefaultSort( + indexPattern, + getServices().uiSettings.get(SORT_DEFAULT_ORDER_SETTING, 'desc') + ); + } + const timeRangeSearchSource = searchSource.create(); timeRangeSearchSource.setField('filter', () => { if (!this.searchScope || !this.input.timeRange) return; @@ -341,7 +350,14 @@ export class SearchEmbeddable // If there is column or sort data on the panel, that means the original columns or sort settings have // been overridden in a dashboard. searchScope.columns = this.input.columns || this.savedSearch.columns; - searchScope.sort = this.input.sort || this.savedSearch.sort; + const savedSearchSort = + this.savedSearch.sort && this.savedSearch.sort.length + ? this.savedSearch.sort + : getDefaultSort( + this.searchScope?.indexPattern, + getServices().uiSettings.get(SORT_DEFAULT_ORDER_SETTING, 'desc') + ); + searchScope.sort = this.input.sort || savedSearchSort; searchScope.sharedItemTitle = this.panelTitle; if (forceFetch || isFetchRequired) { diff --git a/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts b/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts index b2aa3a05d7eb02..4dec1f75ba322b 100644 --- a/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts +++ b/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts @@ -21,6 +21,7 @@ import { getSharingData } from './get_sharing_data'; import { IUiSettingsClient } from 'kibana/public'; import { createSearchSourceMock } from '../../../../data/common/search/search_source/mocks'; import { indexPatternMock } from '../../__mocks__/index_pattern'; +import { SORT_DEFAULT_ORDER_SETTING } from '../../../common'; describe('getSharingData', () => { test('returns valid data for sharing', async () => { @@ -29,7 +30,10 @@ describe('getSharingData', () => { searchSourceMock, { columns: [] }, ({ - get: () => { + get: (key: string) => { + if (key === SORT_DEFAULT_ORDER_SETTING) { + return 'desc'; + } return false; }, } as unknown) as IUiSettingsClient, @@ -57,7 +61,13 @@ describe('getSharingData', () => { }, }, "script_fields": Object {}, - "sort": Array [], + "sort": Array [ + Object { + "_score": Object { + "order": "desc", + }, + }, + ], "stored_fields": undefined, }, "index": "the-index-pattern-title", diff --git a/src/plugins/embeddable/common/types.ts b/src/plugins/embeddable/common/types.ts index 8965446cc85fa7..d893724f616d2b 100644 --- a/src/plugins/embeddable/common/types.ts +++ b/src/plugins/embeddable/common/types.ts @@ -18,8 +18,6 @@ */ import { PersistableStateService, SerializableState } from '../../kibana_utils/common'; -import { Query, TimeRange } from '../../data/common/query'; -import { Filter } from '../../data/common/es_query/filters'; export enum ViewMode { EDIT = 'edit', @@ -53,21 +51,6 @@ export type EmbeddableInput = { */ disableTriggers?: boolean; - /** - * Time range of the chart. - */ - timeRange?: TimeRange; - - /** - * Visualization query string used to narrow down results. - */ - query?: Query; - - /** - * Visualization filters used to narrow down results. - */ - filters?: Filter[]; - /** * Search session id to group searches */ diff --git a/src/plugins/embeddable/public/bootstrap.ts b/src/plugins/embeddable/public/bootstrap.ts index 5c95214ef591bf..efaff42c19e2f5 100644 --- a/src/plugins/embeddable/public/bootstrap.ts +++ b/src/plugins/embeddable/public/bootstrap.ts @@ -18,18 +18,24 @@ */ import { UiActionsSetup } from '../../ui_actions/public'; import { - contextMenuTrigger, - panelBadgeTrigger, - EmbeddableContext, - CONTEXT_MENU_TRIGGER, - PANEL_BADGE_TRIGGER, ACTION_ADD_PANEL, ACTION_CUSTOMIZE_PANEL, - ACTION_INSPECT_PANEL, - REMOVE_PANEL_ACTION, ACTION_EDIT_PANEL, - panelNotificationTrigger, + ACTION_INSPECT_PANEL, + CONTEXT_MENU_TRIGGER, + contextMenuTrigger, + EmbeddableContext, + PANEL_BADGE_TRIGGER, PANEL_NOTIFICATION_TRIGGER, + panelBadgeTrigger, + panelNotificationTrigger, + RangeSelectContext, + REMOVE_PANEL_ACTION, + SELECT_RANGE_TRIGGER, + selectRangeTrigger, + ValueClickContext, + VALUE_CLICK_TRIGGER, + valueClickTrigger, } from './lib'; declare module '../../ui_actions/public' { @@ -37,6 +43,8 @@ declare module '../../ui_actions/public' { [CONTEXT_MENU_TRIGGER]: EmbeddableContext; [PANEL_BADGE_TRIGGER]: EmbeddableContext; [PANEL_NOTIFICATION_TRIGGER]: EmbeddableContext; + [SELECT_RANGE_TRIGGER]: RangeSelectContext; + [VALUE_CLICK_TRIGGER]: ValueClickContext; } export interface ActionContextMapping { @@ -56,4 +64,6 @@ export const bootstrap = (uiActions: UiActionsSetup) => { uiActions.registerTrigger(contextMenuTrigger); uiActions.registerTrigger(panelBadgeTrigger); uiActions.registerTrigger(panelNotificationTrigger); + uiActions.registerTrigger(selectRangeTrigger); + uiActions.registerTrigger(valueClickTrigger); }; diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 0fc7c7965010b2..d537ef2bd0c5c8 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -65,6 +65,8 @@ export { PanelNotFoundError, PanelState, PropertySpec, + SELECT_RANGE_TRIGGER, + VALUE_CLICK_TRIGGER, ViewMode, withEmbeddableSubscription, SavedObjectEmbeddableInput, diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx index c7c71656bceb22..c0e13a84066ca0 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx @@ -24,8 +24,10 @@ import { Embeddable } from './embeddable'; import { EmbeddableOutput, EmbeddableInput } from './i_embeddable'; import { ViewMode } from '../types'; import { ContactCardEmbeddable } from '../test_samples/embeddables/contact_card/contact_card_embeddable'; -import { FilterableEmbeddable } from '../test_samples/embeddables/filterable_embeddable'; -import type { Filter } from '../../../../data/public'; +import { + MockFilter, + FilterableEmbeddable, +} from '../test_samples/embeddables/filterable_embeddable'; class TestClass { constructor() {} @@ -83,7 +85,7 @@ test('Embeddable reload is called if lastReloadRequest input time changes', asyn test('Embeddable reload is called if lastReloadRequest input time changed and new input is used', async () => { const hello = new FilterableEmbeddable({ id: '123', filters: [], lastReloadRequestTime: 0 }); - const aFilter = ({} as unknown) as Filter; + const aFilter = ({} as unknown) as MockFilter; hello.reload = jest.fn(() => { // when reload is called embeddable already has new input expect(hello.getInput().filters).toEqual([aFilter]); diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx index 0361939fd07e6a..cb78fac5471a94 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx @@ -20,6 +20,7 @@ import { ViewMode, EmbeddableOutput, isErrorEmbeddable } from '../../../../'; import { AddPanelAction } from './add_panel_action'; import { + MockFilter, FILTERABLE_EMBEDDABLE, FilterableEmbeddable, FilterableEmbeddableInput, @@ -28,7 +29,6 @@ import { FilterableEmbeddableFactory } from '../../../../test_samples/embeddable import { FilterableContainer } from '../../../../test_samples/embeddables/filterable_container'; import { coreMock } from '../../../../../../../../core/public/mocks'; import { ContactCardEmbeddable } from '../../../../test_samples'; -import { esFilters, Filter } from '../../../../../../../../plugins/data/public'; import { EmbeddableStart } from '../../../../../plugin'; import { embeddablePluginMock } from '../../../../../mocks'; import { defaultTrigger } from '../../../../../../../ui_actions/public/triggers'; @@ -51,8 +51,8 @@ beforeEach(async () => { () => null ); - const derivedFilter: Filter = { - $state: { store: esFilters.FilterStateStore.APP_STATE }, + const derivedFilter: MockFilter = { + $state: { store: 'appState' }, meta: { disabled: false, alias: 'name', negate: false }, query: { match: {} }, }; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx index eb836414489869..b784a46127305c 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx @@ -29,7 +29,6 @@ import { import { inspectorPluginMock } from '../../../../../../../plugins/inspector/public/mocks'; import { EmbeddableOutput, isErrorEmbeddable, ErrorEmbeddable } from '../../../embeddables'; import { of } from '../../../../tests/helpers'; -import { esFilters } from '../../../../../../../plugins/data/public'; import { embeddablePluginMock } from '../../../../mocks'; import { EmbeddableStart } from '../../../../plugin'; @@ -43,7 +42,7 @@ const setupTests = async () => { panels: {}, filters: [ { - $state: { store: esFilters.FilterStateStore.APP_STATE }, + $state: { store: 'appState' }, meta: { disabled: false, alias: 'name', negate: false }, query: { match: {} }, }, diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx index dea4a88bda0823..ce6a1cc20fc4da 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx @@ -21,6 +21,7 @@ import { EmbeddableOutput, isErrorEmbeddable } from '../../../'; import { RemovePanelAction } from './remove_panel_action'; import { EmbeddableStart } from '../../../../plugin'; import { + MockFilter, FILTERABLE_EMBEDDABLE, FilterableEmbeddable, FilterableEmbeddableInput, @@ -29,7 +30,6 @@ import { FilterableEmbeddableFactory } from '../../../test_samples/embeddables/f import { FilterableContainer } from '../../../test_samples/embeddables/filterable_container'; import { ViewMode } from '../../../types'; import { ContactCardEmbeddable } from '../../../test_samples/embeddables/contact_card/contact_card_embeddable'; -import { esFilters, Filter } from '../../../../../../../plugins/data/public'; import { embeddablePluginMock } from '../../../../mocks'; const { setup, doStart } = embeddablePluginMock.createInstance(); @@ -39,8 +39,8 @@ let container: FilterableContainer; let embeddable: FilterableEmbeddable; beforeEach(async () => { - const derivedFilter: Filter = { - $state: { store: esFilters.FilterStateStore.APP_STATE }, + const derivedFilter: MockFilter = { + $state: { store: 'appState' }, meta: { disabled: false, alias: 'name', negate: false }, query: { match: {} }, }; diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts index cbaeddf472d52c..be034d125dceef 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts @@ -23,6 +23,7 @@ import { EmbeddableStateTransfer } from '.'; import { ApplicationStart, PublicAppInfo } from '../../../../../core/public'; import { EMBEDDABLE_EDITOR_STATE_KEY, EMBEDDABLE_PACKAGE_STATE_KEY } from './types'; import { EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY } from './embeddable_state_transfer'; +import { Subject } from 'rxjs'; const createStorage = (): Storage => { const createMockStore = () => { @@ -46,16 +47,24 @@ const createStorage = (): Storage => { describe('embeddable state transfer', () => { let application: jest.Mocked; let stateTransfer: EmbeddableStateTransfer; + let currentAppId$: Subject; let store: Storage; const destinationApp = 'superUltraVisualize'; const originatingApp = 'superUltraTestDashboard'; beforeEach(() => { + currentAppId$ = new Subject(); + currentAppId$.next(originatingApp); const core = coreMock.createStart(); application = core.application; store = createStorage(); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, undefined, store); + stateTransfer = new EmbeddableStateTransfer( + application.navigateToApp, + currentAppId$, + undefined, + store + ); }); it('cannot fetch app name when given no app list', async () => { @@ -67,7 +76,7 @@ describe('embeddable state transfer', () => { ['testId', { title: 'State Transfer Test App Hello' } as PublicAppInfo], ['testId2', { title: 'State Transfer Test App Goodbye' } as PublicAppInfo], ]); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, appsList); + stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, currentAppId$, appsList); expect(stateTransfer.getAppNameFromId('kibanana')).toBeUndefined(); }); @@ -76,7 +85,7 @@ describe('embeddable state transfer', () => { ['testId', { title: 'State Transfer Test App Hello' } as PublicAppInfo], ['testId2', { title: 'State Transfer Test App Goodbye' } as PublicAppInfo], ]); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, appsList); + stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, currentAppId$, appsList); expect(stateTransfer.getAppNameFromId('testId')).toBe('State Transfer Test App Hello'); expect(stateTransfer.getAppNameFromId('testId2')).toBe('State Transfer Test App Goodbye'); }); @@ -107,6 +116,13 @@ describe('embeddable state transfer', () => { }); }); + it('sets isTransferInProgress to true when sending an outgoing editor state', async () => { + await stateTransfer.navigateToEditor(destinationApp, { state: { originatingApp } }); + expect(stateTransfer.isTransferInProgress).toEqual(true); + currentAppId$.next(destinationApp); + expect(stateTransfer.isTransferInProgress).toEqual(false); + }); + it('can send an outgoing embeddable package state', async () => { await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, { state: { type: 'coolestType', input: { savedObjectId: '150' } }, @@ -135,6 +151,15 @@ describe('embeddable state transfer', () => { }); }); + it('sets isTransferInProgress to true when sending an outgoing embeddable package state', async () => { + await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, { + state: { type: 'coolestType', input: { savedObjectId: '150' } }, + }); + expect(stateTransfer.isTransferInProgress).toEqual(true); + currentAppId$.next(destinationApp); + expect(stateTransfer.isTransferInProgress).toEqual(false); + }); + it('can fetch an incoming editor state', async () => { store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, { [EMBEDDABLE_EDITOR_STATE_KEY]: { originatingApp: 'superUltraTestDashboard' }, diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts index 0b34bea8105205..92900059668db6 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts @@ -38,14 +38,20 @@ export const EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY = 'EMBEDDABLE_STATE_TRANSFER' * @public */ export class EmbeddableStateTransfer { + public isTransferInProgress: boolean; private storage: Storage; constructor( private navigateToApp: ApplicationStart['navigateToApp'], + currentAppId$: ApplicationStart['currentAppId$'], private appList?: ReadonlyMap | undefined, customStorage?: Storage ) { this.storage = customStorage ? customStorage : new Storage(sessionStorage); + this.isTransferInProgress = false; + currentAppId$.subscribe(() => { + this.isTransferInProgress = false; + }); } /** @@ -105,6 +111,7 @@ export class EmbeddableStateTransfer { state: EmbeddableEditorState; } ): Promise { + this.isTransferInProgress = true; await this.navigateToWithState(appId, EMBEDDABLE_EDITOR_STATE_KEY, { ...options, appendToExistingState: true, @@ -119,6 +126,7 @@ export class EmbeddableStateTransfer { appId: string, options?: { path?: string; state: EmbeddablePackageState } ): Promise { + this.isTransferInProgress = true; await this.navigateToWithState(appId, EMBEDDABLE_PACKAGE_STATE_KEY, { ...options, appendToExistingState: true, diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container.tsx index db71b94ac855fd..23696612fd82aa 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container.tsx @@ -18,13 +18,13 @@ */ import { Container, ContainerInput } from '../../containers'; -import { Filter } from '../../../../../data/public'; import { EmbeddableStart } from '../../../plugin'; +import { MockFilter } from './filterable_embeddable'; export const FILTERABLE_CONTAINER = 'FILTERABLE_CONTAINER'; export interface FilterableContainerInput extends ContainerInput { - filters: Filter[]; + filters: MockFilter[]; } /** @@ -33,7 +33,7 @@ export interface FilterableContainerInput extends ContainerInput { * here instead */ export type InheritedChildrenInput = { - filters: Filter[]; + filters: MockFilter[]; id?: string; }; diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable.tsx index fd6ea3b9aa2b28..99d21198dd151d 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable.tsx @@ -19,12 +19,18 @@ import { IContainer } from '../../containers'; import { EmbeddableOutput, EmbeddableInput, Embeddable } from '../../embeddables'; -import { Filter } from '../../../../../data/public'; + +/** @internal */ +export interface MockFilter { + $state?: any; + meta: any; + query?: any; +} export const FILTERABLE_EMBEDDABLE = 'FILTERABLE_EMBEDDABLE'; export interface FilterableEmbeddableInput extends EmbeddableInput { - filters: Filter[]; + filters: MockFilter[]; } export class FilterableEmbeddable extends Embeddable { diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index c3b1496b8eca82..d9fb063a5bb569 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -22,8 +22,8 @@ import { Datatable } from '../../../../expressions'; import { Trigger, RowClickContext } from '../../../../ui_actions/public'; import { IEmbeddable } from '..'; -export interface EmbeddableContext { - embeddable: IEmbeddable; +export interface EmbeddableContext { + embeddable: T; } export interface ValueClickContext { @@ -88,6 +88,28 @@ export const panelNotificationTrigger: Trigger<'PANEL_NOTIFICATION_TRIGGER'> = { }), }; +export const SELECT_RANGE_TRIGGER = 'SELECT_RANGE_TRIGGER'; +export const selectRangeTrigger: Trigger<'SELECT_RANGE_TRIGGER'> = { + id: SELECT_RANGE_TRIGGER, + title: i18n.translate('embeddableApi.selectRangeTrigger.title', { + defaultMessage: 'Range selection', + }), + description: i18n.translate('embeddableApi.selectRangeTrigger.description', { + defaultMessage: 'A range of values on the visualization', + }), +}; + +export const VALUE_CLICK_TRIGGER = 'VALUE_CLICK_TRIGGER'; +export const valueClickTrigger: Trigger<'VALUE_CLICK_TRIGGER'> = { + id: VALUE_CLICK_TRIGGER, + title: i18n.translate('embeddableApi.valueClickTrigger.title', { + defaultMessage: 'Single click', + }), + description: i18n.translate('embeddableApi.valueClickTrigger.description', { + defaultMessage: 'A data point click on the visualization', + }), +}; + export const isValueClickTriggerContext = ( context: ChartActionContext ): context is ValueClickContext => context.data && 'data' in context.data; diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx index df24d9c0393fe6..c41ecaabe84797 100644 --- a/src/plugins/embeddable/public/mocks.tsx +++ b/src/plugins/embeddable/public/mocks.tsx @@ -34,7 +34,6 @@ import { coreMock } from '../../../core/public/mocks'; import { UiActionsService } from './lib/ui_actions'; import { CoreStart } from '../../../core/public'; import { Start as InspectorStart } from '../../inspector/public'; -import { dataPluginMock } from '../../data/public/mocks'; import { inspectorPluginMock } from '../../inspector/public/mocks'; import { uiActionsPluginMock } from '../../ui_actions/public/mocks'; @@ -136,13 +135,11 @@ const createInstance = (setupPlugins: Partial = {}) const plugin = new EmbeddablePublicPlugin({} as any); const setup = plugin.setup(coreMock.createSetup(), { uiActions: setupPlugins.uiActions || uiActionsPluginMock.createSetupContract(), - data: dataPluginMock.createSetupContract(), }); const doStart = (startPlugins: Partial = {}) => plugin.start(coreMock.createStart(), { uiActions: startPlugins.uiActions || uiActionsPluginMock.createStartContract(), inspector: inspectorPluginMock.createStartContract(), - data: dataPluginMock.createStartContract(), }); return { plugin, diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx index 5118a1a8818c0e..a417fb3938b8a6 100644 --- a/src/plugins/embeddable/public/plugin.tsx +++ b/src/plugins/embeddable/public/plugin.tsx @@ -19,7 +19,6 @@ import React from 'react'; import { Subscription } from 'rxjs'; import { identity } from 'lodash'; -import { DataPublicPluginSetup, DataPublicPluginStart } from '../../data/public'; import { getSavedObjectFinder, showSaveModal } from '../../saved_objects/public'; import { UiActionsSetup, UiActionsStart } from '../../ui_actions/public'; import { Start as InspectorStart } from '../../inspector/public'; @@ -62,12 +61,10 @@ import { } from '../common/lib'; export interface EmbeddableSetupDependencies { - data: DataPublicPluginSetup; uiActions: UiActionsSetup; } export interface EmbeddableStartDependencies { - data: DataPublicPluginStart; uiActions: UiActionsStart; inspector: InspectorStart; } @@ -144,7 +141,7 @@ export class EmbeddablePublicPlugin implements Plugin { this.embeddableFactories.set( @@ -161,6 +158,7 @@ export class EmbeddablePublicPlugin implements Plugin storage - ? new EmbeddableStateTransfer(core.application.navigateToApp, this.appList, storage) + ? new EmbeddableStateTransfer( + core.application.navigateToApp, + core.application.currentAppId$, + this.appList, + storage + ) : this.stateTransferService, EmbeddablePanel: getEmbeddablePanelHoc(), telemetry: getTelemetryFunction(commonContract), diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 03818fccda0bc4..a401795c498b3f 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -8,48 +8,29 @@ import { Action } from 'history'; import { Action as Action_3 } from 'src/plugins/ui_actions/public'; import { ActionExecutionContext as ActionExecutionContext_2 } from 'src/plugins/ui_actions/public'; import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; -import { ApiResponse as ApiResponse_2 } from '@elastic/elasticsearch'; import { ApplicationStart as ApplicationStart_2 } from 'kibana/public'; -import { Assign } from '@kbn/utility-types'; -import { BehaviorSubject } from 'rxjs'; -import { BfetchPublicSetup } from 'src/plugins/bfetch/public'; import Boom from '@hapi/boom'; import { ConfigDeprecationProvider } from '@kbn/config'; -import { CoreSetup as CoreSetup_2 } from 'src/core/public'; -import { CoreSetup as CoreSetup_3 } from 'kibana/public'; -import { CoreStart as CoreStart_2 } from 'kibana/public'; import * as CSS from 'csstype'; -import { DatatableColumn as DatatableColumn_2 } from 'src/plugins/expressions'; import { EmbeddableStart as EmbeddableStart_2 } from 'src/plugins/embeddable/public/plugin'; -import { Ensure } from '@kbn/utility-types'; import { EnvironmentMode } from '@kbn/config'; -import { ErrorToastOptions as ErrorToastOptions_2 } from 'src/core/public/notifications'; import { EuiBreadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; -import { EuiComboBoxProps } from '@elastic/eui'; import { EuiConfirmModalProps } from '@elastic/eui'; import { EuiContextMenuPanelDescriptor } from '@elastic/eui'; import { EuiFlyoutSize } from '@elastic/eui'; import { EuiGlobalToastListToast } from '@elastic/eui'; import { EventEmitter } from 'events'; -import { ExpressionAstExpression } from 'src/plugins/expressions/common'; import { History } from 'history'; import { Href } from 'history'; -import { HttpSetup as HttpSetup_2 } from 'kibana/public'; import { I18nStart as I18nStart_2 } from 'src/core/public'; import { IconType } from '@elastic/eui'; -import { ISearchOptions } from 'src/plugins/data/public'; -import { ISearchSource } from 'src/plugins/data/public'; -import { IStorageWrapper as IStorageWrapper_2 } from 'src/plugins/kibana_utils/public'; -import { IUiSettingsClient as IUiSettingsClient_2 } from 'src/core/public'; import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { Location } from 'history'; import { LocationDescriptorObject } from 'history'; import { Logger } from '@kbn/logging'; import { LogMeta } from '@kbn/logging'; import { MaybePromise } from '@kbn/utility-types'; -import { Moment } from 'moment'; -import { NameList } from 'elasticsearch'; import { NotificationsStart as NotificationsStart_2 } from 'src/core/public'; import { Observable } from 'rxjs'; import { Optional } from '@kbn/utility-types'; @@ -57,39 +38,23 @@ import { OverlayStart as OverlayStart_2 } from 'src/core/public'; import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; import { PluginInitializerContext } from 'src/core/public'; -import { PluginInitializerContext as PluginInitializerContext_3 } from 'kibana/public'; import * as PropTypes from 'prop-types'; -import { PublicContract } from '@kbn/utility-types'; import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicUiSettingsParams } from 'src/core/server/types'; import React from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; -import { RequestAdapter as RequestAdapter_2 } from 'src/plugins/inspector/common'; -import { Required } from '@kbn/utility-types'; import * as Rx from 'rxjs'; -import { SavedObject as SavedObject_2 } from 'kibana/server'; -import { SavedObject as SavedObject_3 } from 'src/core/server'; import { SavedObjectAttributes } from 'kibana/server'; import { SavedObjectAttributes as SavedObjectAttributes_2 } from 'src/core/public'; import { SavedObjectAttributes as SavedObjectAttributes_3 } from 'kibana/public'; -import { SavedObjectsClientContract as SavedObjectsClientContract_3 } from 'src/core/public'; -import { SavedObjectsFindOptions as SavedObjectsFindOptions_3 } from 'kibana/public'; -import { SavedObjectsFindResponse as SavedObjectsFindResponse_2 } from 'kibana/server'; -import { Search } from '@elastic/elasticsearch/api/requestParams'; -import { SearchResponse } from 'elasticsearch'; -import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; import { ShallowPromise } from '@kbn/utility-types'; import { SimpleSavedObject as SimpleSavedObject_2 } from 'src/core/public'; import { Start as Start_2 } from 'src/plugins/inspector/public'; -import { StartServicesAccessor as StartServicesAccessor_2 } from 'kibana/public'; -import { ToastInputFields as ToastInputFields_2 } from 'src/core/public/notifications'; -import { ToastsSetup as ToastsSetup_2 } from 'kibana/public'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; import { TypeOf } from '@kbn/config-schema'; import { UiComponent } from 'src/plugins/kibana_utils/public'; -import { UiCounterMetricType } from '@kbn/analytics'; import { UnregisterCallback } from 'history'; import { UserProvidedValues } from 'src/core/server/types'; @@ -348,7 +313,7 @@ export abstract class Embeddable { +export class EmbeddableChildPanel extends React.Component { constructor(props: EmbeddableChildPanelProps); // (undocumented) [panel: string]: any; @@ -381,9 +346,9 @@ export interface EmbeddableChildPanelProps { // Warning: (ae-missing-release-tag) "EmbeddableContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface EmbeddableContext { +export interface EmbeddableContext { // (undocumented) - embeddable: IEmbeddable; + embeddable: T; } // @public @@ -444,9 +409,6 @@ export type EmbeddableInput = { enhancements?: SerializableState; disabledActions?: string[]; disableTriggers?: boolean; - timeRange?: TimeRange; - query?: Query; - filters?: Filter[]; searchSessionId?: string; }; @@ -501,7 +463,7 @@ export interface EmbeddablePackageState { // Warning: (ae-missing-release-tag) "EmbeddablePanel" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class EmbeddablePanel extends React.Component { +export class EmbeddablePanel extends React.Component { constructor(props: Props); // (undocumented) closeMyContextMenuPanel: () => void; @@ -571,10 +533,6 @@ export interface EmbeddableSetup { // // @public (undocumented) export interface EmbeddableSetupDependencies { - // Warning: (ae-forgotten-export) The symbol "DataPublicPluginSetup" needs to be exported by the entry point index.d.ts - // - // (undocumented) - data: DataPublicPluginSetup; // Warning: (ae-forgotten-export) The symbol "UiActionsSetup" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -610,10 +568,6 @@ export interface EmbeddableStart extends PersistableStateService | undefined, customStorage?: Storage); + constructor(navigateToApp: ApplicationStart['navigateToApp'], currentAppId$: ApplicationStart['currentAppId$'], appList?: ReadonlyMap | undefined, customStorage?: Storage); // (undocumented) clearEditorState(): void; getAppNameFromId: (appId: string) => string | undefined; getIncomingEditorState(removeAfterFetch?: boolean): EmbeddableEditorState | undefined; getIncomingEmbeddablePackage(removeAfterFetch?: boolean): EmbeddablePackageState | undefined; + // (undocumented) + isTransferInProgress: boolean; // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ApplicationStart" navigateToEditor(appId: string, options?: { path?: string; @@ -713,7 +669,7 @@ export interface IEmbeddable context is EmbeddableContext; +export const isContextMenuTriggerContext: (context: unknown) => context is EmbeddableContext>; // Warning: (ae-missing-release-tag) "isEmbeddable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -867,6 +823,16 @@ export interface SavedObjectEmbeddableInput extends EmbeddableInput { savedObjectId: string; } +// Warning: (ae-missing-release-tag) "SELECT_RANGE_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const SELECT_RANGE_TRIGGER = "SELECT_RANGE_TRIGGER"; + +// Warning: (ae-missing-release-tag) "VALUE_CLICK_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const VALUE_CLICK_TRIGGER = "VALUE_CLICK_TRIGGER"; + // Warning: (ae-missing-release-tag) "ValueClickContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -910,10 +876,7 @@ export const withEmbeddableSubscription: { test('Explicit embeddable input mapped to undefined will default to inherited', async () => { const { start } = await creatHelloWorldContainerAndEmbeddable(); - const derivedFilter: Filter = { - $state: { store: esFilters.FilterStateStore.APP_STATE }, + const derivedFilter: MockFilter = { + $state: { store: 'appState' }, meta: { disabled: false, alias: 'name', negate: false }, query: { match: {} }, }; diff --git a/src/plugins/embeddable/public/tests/explicit_input.test.ts b/src/plugins/embeddable/public/tests/explicit_input.test.ts index 24785dd50a0322..531fbcee94db6e 100644 --- a/src/plugins/embeddable/public/tests/explicit_input.test.ts +++ b/src/plugins/embeddable/public/tests/explicit_input.test.ts @@ -20,6 +20,7 @@ import { skip } from 'rxjs/operators'; import { testPlugin } from './test_plugin'; import { + MockFilter, FILTERABLE_EMBEDDABLE, FilterableEmbeddableInput, } from '../lib/test_samples/embeddables/filterable_embeddable'; @@ -34,7 +35,6 @@ import { FilterableContainer } from '../lib/test_samples/embeddables/filterable_ import { isErrorEmbeddable } from '../lib'; import { HelloWorldContainer } from '../lib/test_samples/embeddables/hello_world_container'; import { coreMock } from '../../../../core/public/mocks'; -import { esFilters, Filter } from '../../../../plugins/data/public'; import { createEmbeddablePanelMock } from '../mocks'; const { setup, doStart, coreStart, uiActions } = testPlugin( @@ -56,8 +56,8 @@ setup.registerEmbeddableFactory( const start = doStart(); test('Explicit embeddable input mapped to undefined will default to inherited', async () => { - const derivedFilter: Filter = { - $state: { store: esFilters.FilterStateStore.APP_STATE }, + const derivedFilter: MockFilter = { + $state: { store: 'appState' }, meta: { disabled: false, alias: 'name', negate: false }, query: { match: {} }, }; diff --git a/src/plugins/embeddable/public/tests/test_plugin.ts b/src/plugins/embeddable/public/tests/test_plugin.ts index 2c298b437a118b..74bb70e913bccc 100644 --- a/src/plugins/embeddable/public/tests/test_plugin.ts +++ b/src/plugins/embeddable/public/tests/test_plugin.ts @@ -21,7 +21,6 @@ import { CoreSetup, CoreStart } from 'src/core/public'; import { UiActionsStart } from '../../../ui_actions/public'; import { uiActionsPluginMock } from '../../../ui_actions/public/mocks'; import { inspectorPluginMock } from '../../../inspector/public/mocks'; -import { dataPluginMock } from '../../../data/public/mocks'; import { coreMock } from '../../../../core/public/mocks'; import { EmbeddablePublicPlugin, EmbeddableSetup, EmbeddableStart } from '../plugin'; @@ -42,7 +41,6 @@ export const testPlugin = ( const initializerContext = {} as any; const plugin = new EmbeddablePublicPlugin(initializerContext); const setup = plugin.setup(coreSetup, { - data: dataPluginMock.createSetupContract(), uiActions: uiActions.setup, }); @@ -53,7 +51,6 @@ export const testPlugin = ( setup, doStart: (anotherCoreStart: CoreStart = coreStart) => { const start = plugin.start(anotherCoreStart, { - data: dataPluginMock.createStartContract(), inspector: inspectorPluginMock.createStartContract(), uiActions: uiActionsPluginMock.createStartContract(), }); diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.tsx b/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.tsx index 72e2f51c37e4c4..19af93b67aca03 100644 --- a/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.tsx +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.tsx @@ -67,6 +67,7 @@ interface Props { fieldToPreferredValueMap: FieldToValueMap; frequency: Frequency; }) => void; + autoFocus?: boolean; } type State = FieldToValueMap; @@ -234,6 +235,7 @@ export class CronEditor extends Component { fullWidth > ) => diff --git a/src/plugins/expressions/common/expression_renderers/types.ts b/src/plugins/expressions/common/expression_renderers/types.ts index 88aca4c07ee312..fca1694747ce2d 100644 --- a/src/plugins/expressions/common/expression_renderers/types.ts +++ b/src/plugins/expressions/common/expression_renderers/types.ts @@ -17,8 +17,6 @@ * under the License. */ -import { PersistedState } from 'src/plugins/visualizations/public'; - export interface ExpressionRenderDefinition { /** * Technical name of the renderer, used as ID to identify renderer in @@ -84,5 +82,10 @@ export interface IInterpreterRenderHandlers { event: (event: any) => void; hasCompatibleActions?: (event: any) => Promise; getRenderMode: () => RenderMode; - uiState?: PersistedState; + /** + * This uiState interface is actually `PersistedState` from the visualizations plugin, + * but expressions cannot know about vis or it creates a mess of circular dependencies. + * Downstream consumers of the uiState handler will need to cast for now. + */ + uiState?: unknown; } diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index bb1f5dd9270d5f..404df2db019a1e 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -12,7 +12,6 @@ import { EventEmitter } from 'events'; import { KibanaRequest } from 'src/core/server'; import { Observable } from 'rxjs'; import { PackageInfo } from '@kbn/config'; -import { PersistedState } from 'src/plugins/visualizations/public'; import { Plugin as Plugin_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public'; import React from 'react'; @@ -924,8 +923,7 @@ export interface IInterpreterRenderHandlers { onDestroy: (fn: () => void) => void; // (undocumented) reload: () => void; - // (undocumented) - uiState?: PersistedState; + uiState?: unknown; // (undocumented) update: (params: any) => void; } diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx index e52d4d153882ff..4ebd626e70fc3e 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -146,6 +146,44 @@ describe('ExpressionRenderer', () => { instance.unmount(); }); + it('waits for debounce period on other loader option change if specified', () => { + jest.useFakeTimers(); + + const refreshSubject = new Subject(); + const loaderUpdate = jest.fn(); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$: new Subject(), + data$: new Subject(), + loading$: new Subject(), + update: loaderUpdate, + destroy: jest.fn(), + }; + }); + + const instance = mount( + + ); + + instance.setProps({ searchContext: { from: 'now-30m', to: 'now' } }); + + expect(loaderUpdate).toHaveBeenCalledTimes(1); + + act(() => { + jest.runAllTimers(); + }); + + expect(loaderUpdate).toHaveBeenCalledTimes(2); + + instance.unmount(); + }); + it('should display a custom error message if the user provides one and then remove it after successful render', () => { const dataSubject = new Subject(); const data$ = dataSubject.asObservable().pipe(share()); diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index 894325c8b65f7d..eac2371ec66d0c 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -90,21 +90,23 @@ export const ReactExpressionRenderer = ({ null ); const [debouncedExpression, setDebouncedExpression] = useState(expression); - useEffect(() => { + const [waitingForDebounceToComplete, setDebouncePending] = useState(false); + useShallowCompareEffect(() => { if (debounce === undefined) { return; } + setDebouncePending(true); const handler = setTimeout(() => { setDebouncedExpression(expression); + setDebouncePending(false); }, debounce); return () => { clearTimeout(handler); }; - }, [expression, debounce]); + }, [expression, expressionLoaderOptions, debounce]); const activeExpression = debounce !== undefined ? debouncedExpression : expression; - const waitingForDebounceToComplete = debounce !== undefined && expression !== debouncedExpression; /* eslint-disable react-hooks/exhaustive-deps */ // OK to ignore react-hooks/exhaustive-deps because options update is handled by calling .update() @@ -182,12 +184,16 @@ export const ReactExpressionRenderer = ({ // Re-fetch data automatically when the inputs change useShallowCompareEffect( () => { - if (expressionLoaderRef.current) { + // only update the loader if the debounce period is over + if (expressionLoaderRef.current && !waitingForDebounceToComplete) { expressionLoaderRef.current.update(activeExpression, expressionLoaderOptions); } }, - // when expression is changed by reference and when any other loaderOption is changed by reference - [{ activeExpression, ...expressionLoaderOptions }] + // when debounced, wait for debounce status to change to update loader. + // Otherwise, update when expression is changed by reference and when any other loaderOption is changed by reference + debounce === undefined + ? [{ activeExpression, ...expressionLoaderOptions }] + : [{ waitingForDebounceToComplete }] ); /* eslint-enable react-hooks/exhaustive-deps */ diff --git a/src/plugins/expressions/server/server.api.md b/src/plugins/expressions/server/server.api.md index 7c1ab11f75027c..8b8678371dd838 100644 --- a/src/plugins/expressions/server/server.api.md +++ b/src/plugins/expressions/server/server.api.md @@ -10,7 +10,6 @@ import { Ensure } from '@kbn/utility-types'; import { EventEmitter } from 'events'; import { KibanaRequest } from 'src/core/server'; import { Observable } from 'rxjs'; -import { PersistedState } from 'src/plugins/visualizations/public'; import { Plugin as Plugin_2 } from 'src/core/server'; import { PluginInitializerContext } from 'src/core/server'; import { UnwrapPromiseOrReturn } from '@kbn/utility-types'; @@ -741,8 +740,7 @@ export interface IInterpreterRenderHandlers { onDestroy: (fn: () => void) => void; // (undocumented) reload: () => void; - // (undocumented) - uiState?: PersistedState; + uiState?: unknown; // (undocumented) update: (params: any) => void; } diff --git a/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.test.ts b/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.test.ts index a7681e17664272..64f1088dc33923 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.test.ts @@ -16,14 +16,25 @@ * specific language governing permissions and limitations * under the License. */ - +import { elasticsearchServiceMock } from '../../../../../../src/core/server/mocks'; import { getSavedObjectsCounts } from './get_saved_object_counts'; +export function mockGetSavedObjectsCounts(params: any) { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + esClient.search.mockResolvedValue( + // @ts-ignore we only care about the response body + { + body: { ...params }, + } + ); + return esClient; +} + describe('getSavedObjectsCounts', () => { test('Get all the saved objects equal to 0 because no results were found', async () => { - const callCluster = jest.fn(() => ({})); + const esClient = mockGetSavedObjectsCounts({}); - const results = await getSavedObjectsCounts(callCluster as any, '.kibana'); + const results = await getSavedObjectsCounts(esClient, '.kibana'); expect(results).toStrictEqual({ dashboard: { total: 0 }, visualization: { total: 0 }, @@ -35,7 +46,7 @@ describe('getSavedObjectsCounts', () => { }); test('Merge the zeros with the results', async () => { - const callCluster = jest.fn(() => ({ + const esClient = mockGetSavedObjectsCounts({ aggregations: { types: { buckets: [ @@ -46,9 +57,9 @@ describe('getSavedObjectsCounts', () => { ], }, }, - })); + }); - const results = await getSavedObjectsCounts(callCluster as any, '.kibana'); + const results = await getSavedObjectsCounts(esClient, '.kibana'); expect(results).toStrictEqual({ dashboard: { total: 1 }, visualization: { total: 0 }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts b/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts index e88d90fe5b24ba..65cc3643a88cb6 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts @@ -27,7 +27,7 @@ */ import { snakeCase } from 'lodash'; -import { LegacyAPICaller } from 'kibana/server'; +import { ElasticsearchClient } from 'src/core/server'; const TYPES = [ 'dashboard', @@ -48,7 +48,7 @@ export interface KibanaSavedObjectCounts { } export async function getSavedObjectsCounts( - callCluster: LegacyAPICaller, + esClient: ElasticsearchClient, kibanaIndex: string // Typically '.kibana'. We might need a way to obtain it from the SavedObjects client (or the SavedObjects client to provide a way to run aggregations?) ): Promise { const savedObjectCountSearchParams = { @@ -67,9 +67,9 @@ export async function getSavedObjectsCounts( }, }, }; - const resp = await callCluster('search', savedObjectCountSearchParams); + const { body } = await esClient.search(savedObjectCountSearchParams); const buckets: Array<{ key: string; doc_count: number }> = - resp.aggregations?.types?.buckets || []; + body.aggregations?.types?.buckets || []; // Initialise the object with all zeros for all the types const allZeros: KibanaSavedObjectCounts = TYPES.reduce( diff --git a/src/plugins/kibana_usage_collection/server/collectors/kibana/index.test.ts b/src/plugins/kibana_usage_collection/server/collectors/kibana/index.test.ts index 83cac1d456a3ae..dee9ca4d32c5fb 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/kibana/index.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/kibana/index.test.ts @@ -20,12 +20,13 @@ import { loggingSystemMock, pluginInitializerContextConfigMock, + elasticsearchServiceMock, } from '../../../../../core/server/mocks'; import { Collector, + createCollectorFetchContextMock, createUsageCollectionSetupMock, } from '../../../../usage_collection/server/usage_collection.mock'; -import { createCollectorFetchContextMock } from '../../../../usage_collection/server/mocks'; import { registerKibanaUsageCollector } from './'; const logger = loggingSystemMock.createLogger(); @@ -43,7 +44,9 @@ describe('telemetry_kibana', () => { const getMockFetchClients = (hits?: unknown[]) => { const fetchParamsMock = createCollectorFetchContextMock(); - fetchParamsMock.callCluster.mockResolvedValue({ hits: { hits } }); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + esClient.search.mockResolvedValue({ body: { hits: { hits } } } as any); + fetchParamsMock.esClient = esClient; return fetchParamsMock; }; diff --git a/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts index 6c2e0a2c926ad0..5dd39d172e1c2c 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts @@ -43,13 +43,13 @@ export function getKibanaUsageCollector( graph_workspace: { total: { type: 'long' } }, timelion_sheet: { total: { type: 'long' } }, }, - async fetch({ callCluster }) { + async fetch({ esClient }) { const { kibana: { index }, } = await legacyConfig$.pipe(take(1)).toPromise(); return { index, - ...(await getSavedObjectsCounts(callCluster, index)), + ...(await getSavedObjectsCounts(esClient, index)), }; }, }); diff --git a/src/plugins/telemetry/common/constants.ts b/src/plugins/telemetry/common/constants.ts index fc77332c18fc90..2fa8b32f682913 100644 --- a/src/plugins/telemetry/common/constants.ts +++ b/src/plugins/telemetry/common/constants.ts @@ -49,7 +49,7 @@ export const LOCALSTORAGE_KEY = 'telemetry.data'; /** * Link to Advanced Settings. */ -export const PATH_TO_ADVANCED_SETTINGS = 'management/kibana/settings'; +export const PATH_TO_ADVANCED_SETTINGS = '/app/management/kibana/settings'; /** * Link to the Elastic Telemetry privacy statement. diff --git a/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap b/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap index 62998da73d6f93..897e3b2761c744 100644 --- a/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap +++ b/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap @@ -10,7 +10,7 @@ exports[`OptInDetailsComponent renders as expected 1`] = ` values={ Object { "disableLink": { it('renders as expected', () => { - expect(shallowWithIntl( {}} />)).toMatchSnapshot(); + expect( + shallowWithIntl( {}} http={mockHttp} />) + ).toMatchSnapshot(); }); it('fires the "onSeenBanner" prop when a link is clicked', () => { const onLinkClick = jest.fn(); - const component = shallowWithIntl(); + const component = shallowWithIntl( + + ); const button = component.findWhere((n) => n.type() === EuiButton); diff --git a/src/plugins/telemetry/public/components/opted_in_notice_banner.tsx b/src/plugins/telemetry/public/components/opted_in_notice_banner.tsx index 090893964c8810..46ae17171203ce 100644 --- a/src/plugins/telemetry/public/components/opted_in_notice_banner.tsx +++ b/src/plugins/telemetry/public/components/opted_in_notice_banner.tsx @@ -24,14 +24,18 @@ import { EuiButton, EuiLink, EuiCallOut, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { PATH_TO_ADVANCED_SETTINGS, PRIVACY_STATEMENT_URL } from '../../common/constants'; +import { HttpSetup } from '../../../../core/public'; interface Props { + http: HttpSetup; onSeenBanner: () => any; } export class OptedInNoticeBanner extends React.PureComponent { render() { - const { onSeenBanner } = this.props; + const { onSeenBanner, http } = this.props; + const basePath = http.basePath.get(); + const bannerTitle = i18n.translate('telemetry.telemetryOptedInNoticeTitle', { defaultMessage: 'Help us improve the Elastic Stack', }); @@ -56,7 +60,7 @@ export class OptedInNoticeBanner extends React.PureComponent { ), disableLink: ( - + { it('adds a banner to banners with priority of 10000', () => { const bannerID = 'brucer-wayne'; const overlays = overlayServiceMock.createStartContract(); + const mockHttp = httpServiceMock.createStartContract(); overlays.banners.add.mockReturnValue(bannerID); const returnedBannerId = renderOptedInNoticeBanner({ + http: mockHttp, onSeen: jest.fn(), overlays, }); diff --git a/src/plugins/telemetry/public/services/telemetry_notifications/render_opted_in_notice_banner.tsx b/src/plugins/telemetry/public/services/telemetry_notifications/render_opted_in_notice_banner.tsx index e63e46af6e8ca3..e1feea4b6cbe11 100644 --- a/src/plugins/telemetry/public/services/telemetry_notifications/render_opted_in_notice_banner.tsx +++ b/src/plugins/telemetry/public/services/telemetry_notifications/render_opted_in_notice_banner.tsx @@ -23,11 +23,12 @@ import { OptedInNoticeBanner } from '../../components/opted_in_notice_banner'; import { toMountPoint } from '../../../../kibana_react/public'; interface RenderBannerConfig { + http: CoreStart['http']; overlays: CoreStart['overlays']; onSeen: () => void; } -export function renderOptedInNoticeBanner({ onSeen, overlays }: RenderBannerConfig) { - const mount = toMountPoint(); +export function renderOptedInNoticeBanner({ onSeen, overlays, http }: RenderBannerConfig) { + const mount = toMountPoint(); const bannerId = overlays.banners.add(mount, 10000); return bannerId; diff --git a/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts b/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts index fc44a4db7cf5e8..6ebbfcfb913368 100644 --- a/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts +++ b/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts @@ -23,18 +23,21 @@ import { renderOptInBanner } from './render_opt_in_banner'; import { TelemetryService } from '../telemetry_service'; interface TelemetryNotificationsConstructor { + http: CoreStart['http']; overlays: CoreStart['overlays']; telemetryService: TelemetryService; } export class TelemetryNotifications { + private readonly http: CoreStart['http']; private readonly overlays: CoreStart['overlays']; private readonly telemetryService: TelemetryService; private optedInNoticeBannerId?: string; private optInBannerId?: string; - constructor({ overlays, telemetryService }: TelemetryNotificationsConstructor) { + constructor({ http, overlays, telemetryService }: TelemetryNotificationsConstructor) { this.telemetryService = telemetryService; + this.http = http; this.overlays = overlays; } @@ -46,6 +49,7 @@ export class TelemetryNotifications { public renderOptedInNoticeBanner = (): void => { const bannerId = renderOptedInNoticeBanner({ + http: this.http, onSeen: this.setOptedInNoticeSeen, overlays: this.overlays, }); diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index d223c0abcccb7a..7890e4bab44a3c 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -40,12 +40,6 @@ export { export { Trigger, TriggerContext, - SELECT_RANGE_TRIGGER, - selectRangeTrigger, - VALUE_CLICK_TRIGGER, - valueClickTrigger, - APPLY_FILTER_TRIGGER, - applyFilterTrigger, VISUALIZE_FIELD_TRIGGER, visualizeFieldTrigger, VISUALIZE_GEO_FIELD_TRIGGER, diff --git a/src/plugins/ui_actions/public/plugin.ts b/src/plugins/ui_actions/public/plugin.ts index fdb75e9a426e9e..84a7ae45fc7b81 100644 --- a/src/plugins/ui_actions/public/plugin.ts +++ b/src/plugins/ui_actions/public/plugin.ts @@ -20,14 +20,7 @@ import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; import { PublicMethodsOf } from '@kbn/utility-types'; import { UiActionsService } from './service'; -import { - selectRangeTrigger, - valueClickTrigger, - rowClickTrigger, - applyFilterTrigger, - visualizeFieldTrigger, - visualizeGeoFieldTrigger, -} from './triggers'; +import { rowClickTrigger, visualizeFieldTrigger, visualizeGeoFieldTrigger } from './triggers'; export type UiActionsSetup = Pick< UiActionsService, @@ -47,10 +40,7 @@ export class UiActionsPlugin implements Plugin { constructor(initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup): UiActionsSetup { - this.service.registerTrigger(selectRangeTrigger); - this.service.registerTrigger(valueClickTrigger); this.service.registerTrigger(rowClickTrigger); - this.service.registerTrigger(applyFilterTrigger); this.service.registerTrigger(visualizeFieldTrigger); this.service.registerTrigger(visualizeGeoFieldTrigger); return this.service; diff --git a/src/plugins/ui_actions/public/public.api.md b/src/plugins/ui_actions/public/public.api.md index 2384dfab13c8c1..808cb1f3fbca03 100644 --- a/src/plugins/ui_actions/public/public.api.md +++ b/src/plugins/ui_actions/public/public.api.md @@ -8,14 +8,11 @@ import { CoreSetup } from 'src/core/public'; import { CoreStart } from 'src/core/public'; import { EnvironmentMode } from '@kbn/config'; import { EuiContextMenuPanelDescriptor } from '@elastic/eui'; -import { EventEmitter } from 'events'; -import { Observable } from 'rxjs'; import { PackageInfo } from '@kbn/config'; import { Plugin } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public'; import { PublicMethodsOf } from '@kbn/utility-types'; import React from 'react'; -import * as Rx from 'rxjs'; import { UiComponent } from 'src/plugins/kibana_utils/public'; // Warning: (ae-forgotten-export) The symbol "BaseContext" needs to be exported by the entry point index.d.ts @@ -95,16 +92,6 @@ export interface ActionExecutionMeta { // @public (undocumented) export type ActionType = keyof ActionContextMapping; -// Warning: (ae-missing-release-tag) "APPLY_FILTER_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const APPLY_FILTER_TRIGGER = "FILTER_TRIGGER"; - -// Warning: (ae-missing-release-tag) "applyFilterTrigger" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const applyFilterTrigger: Trigger<'FILTER_TRIGGER'>; - // Warning: (ae-forgotten-export) The symbol "BuildContextMenuParams" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "buildContextMenuForActions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -148,10 +135,8 @@ export interface RowClickContext { table: Datatable; columns?: string[]; }; - // Warning: (ae-forgotten-export) The symbol "IEmbeddable" needs to be exported by the entry point index.d.ts - // // (undocumented) - embeddable?: IEmbeddable; + embeddable?: unknown; } // Warning: (ae-missing-release-tag) "rowClickTrigger" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -159,16 +144,6 @@ export interface RowClickContext { // @public (undocumented) export const rowClickTrigger: Trigger<'ROW_CLICK_TRIGGER'>; -// Warning: (ae-missing-release-tag) "SELECT_RANGE_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const SELECT_RANGE_TRIGGER = "SELECT_RANGE_TRIGGER"; - -// Warning: (ae-missing-release-tag) "selectRangeTrigger" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const selectRangeTrigger: Trigger<'SELECT_RANGE_TRIGGER'>; - // Warning: (ae-missing-release-tag) "Trigger" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -192,20 +167,8 @@ export interface TriggerContextMapping { // // (undocumented) [DEFAULT_TRIGGER]: TriggerContext_2; - // Warning: (ae-forgotten-export) The symbol "ApplyGlobalFilterActionContext" needs to be exported by the entry point index.d.ts - // - // (undocumented) - [APPLY_FILTER_TRIGGER]: ApplyGlobalFilterActionContext; // (undocumented) [ROW_CLICK_TRIGGER]: RowClickContext; - // Warning: (ae-forgotten-export) The symbol "RangeSelectContext" needs to be exported by the entry point index.d.ts - // - // (undocumented) - [SELECT_RANGE_TRIGGER]: RangeSelectContext; - // Warning: (ae-forgotten-export) The symbol "ValueClickContext" needs to be exported by the entry point index.d.ts - // - // (undocumented) - [VALUE_CLICK_TRIGGER]: ValueClickContext; // (undocumented) [VISUALIZE_FIELD_TRIGGER]: VisualizeFieldContext; // (undocumented) @@ -262,35 +225,35 @@ export class UiActionsService { // // (undocumented) protected readonly actions: ActionRegistry; - readonly addTriggerAction: (triggerId: T, action: UiActionsActionDefinition | Action) => void; + readonly addTriggerAction: (triggerId: T, action: UiActionsActionDefinition | Action) => void; // (undocumented) - readonly attachAction: (triggerId: T, actionId: string) => void; + readonly attachAction: (triggerId: T, actionId: string) => void; readonly clear: () => void; // (undocumented) readonly detachAction: (triggerId: TriggerId, actionId: string) => void; // @deprecated (undocumented) - readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise; + readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise; // Warning: (ae-forgotten-export) The symbol "UiActionsExecutionService" needs to be exported by the entry point index.d.ts // // (undocumented) readonly executionService: UiActionsExecutionService; readonly fork: () => UiActionsService; // (undocumented) - readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">; + readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel">; // Warning: (ae-forgotten-export) The symbol "TriggerContract" needs to be exported by the entry point index.d.ts // // (undocumented) - readonly getTrigger: (triggerId: T) => TriggerContract; + readonly getTrigger: (triggerId: T) => TriggerContract; // (undocumented) - readonly getTriggerActions: (triggerId: T) => Action[]; + readonly getTriggerActions: (triggerId: T) => Action[]; // (undocumented) - readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; + readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; // (undocumented) readonly hasAction: (actionId: string) => boolean; // Warning: (ae-forgotten-export) The symbol "ActionContext" needs to be exported by the entry point index.d.ts // // (undocumented) - readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">; + readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel">; // (undocumented) readonly registerTrigger: (trigger: Trigger) => void; // Warning: (ae-forgotten-export) The symbol "TriggerRegistry" needs to be exported by the entry point index.d.ts @@ -326,16 +289,6 @@ export type UiActionsSetup = Pick; -// Warning: (ae-missing-release-tag) "VALUE_CLICK_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const VALUE_CLICK_TRIGGER = "VALUE_CLICK_TRIGGER"; - -// Warning: (ae-missing-release-tag) "valueClickTrigger" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const valueClickTrigger: Trigger<'VALUE_CLICK_TRIGGER'>; - // Warning: (ae-missing-release-tag) "VISUALIZE_FIELD_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -371,7 +324,7 @@ export const visualizeGeoFieldTrigger: Trigger<'VISUALIZE_GEO_FIELD_TRIGGER'>; // Warnings were encountered during analysis: // -// src/plugins/ui_actions/public/triggers/row_click_trigger.ts:45:5 - (ae-forgotten-export) The symbol "Datatable" needs to be exported by the entry point index.d.ts +// src/plugins/ui_actions/public/triggers/row_click_trigger.ts:46:5 - (ae-forgotten-export) The symbol "Datatable" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/ui_actions/public/triggers/index.ts b/src/plugins/ui_actions/public/triggers/index.ts index ecbf4d1f7b9881..6bba87e85eb95e 100644 --- a/src/plugins/ui_actions/public/triggers/index.ts +++ b/src/plugins/ui_actions/public/triggers/index.ts @@ -20,10 +20,7 @@ export * from './trigger'; export * from './trigger_contract'; export * from './trigger_internal'; -export * from './select_range_trigger'; -export * from './value_click_trigger'; export * from './row_click_trigger'; -export * from './apply_filter_trigger'; export * from './visualize_field_trigger'; export * from './visualize_geo_field_trigger'; export * from './default_trigger'; diff --git a/src/plugins/ui_actions/public/triggers/row_click_trigger.ts b/src/plugins/ui_actions/public/triggers/row_click_trigger.ts index 87bca03f8c3bab..0fc261b3e1fb35 100644 --- a/src/plugins/ui_actions/public/triggers/row_click_trigger.ts +++ b/src/plugins/ui_actions/public/triggers/row_click_trigger.ts @@ -18,7 +18,6 @@ */ import { i18n } from '@kbn/i18n'; -import { IEmbeddable } from '../../../embeddable/public'; import { Trigger } from '.'; import { Datatable } from '../../../expressions'; @@ -35,7 +34,9 @@ export const rowClickTrigger: Trigger<'ROW_CLICK_TRIGGER'> = { }; export interface RowClickContext { - embeddable?: IEmbeddable; + // Need to make this unknown to prevent circular dependencies. + // Apps using this property will need to cast to `IEmbeddable`. + embeddable?: unknown; data: { /** * Row index, starting from 0, where user clicked. diff --git a/src/plugins/ui_actions/public/triggers/select_range_trigger.ts b/src/plugins/ui_actions/public/triggers/select_range_trigger.ts deleted file mode 100644 index 312e75314bd926..00000000000000 --- a/src/plugins/ui_actions/public/triggers/select_range_trigger.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import { Trigger } from '.'; - -export const SELECT_RANGE_TRIGGER = 'SELECT_RANGE_TRIGGER'; -export const selectRangeTrigger: Trigger<'SELECT_RANGE_TRIGGER'> = { - id: SELECT_RANGE_TRIGGER, - title: i18n.translate('uiActions.triggers.selectRangeTitle', { - defaultMessage: 'Range selection', - }), - description: i18n.translate('uiActions.triggers.selectRangeDescription', { - defaultMessage: 'A range of values on the visualization', - }), -}; diff --git a/src/plugins/ui_actions/public/triggers/trigger.ts b/src/plugins/ui_actions/public/triggers/trigger.ts index 2c019b09881d17..1b1231c132dde7 100644 --- a/src/plugins/ui_actions/public/triggers/trigger.ts +++ b/src/plugins/ui_actions/public/triggers/trigger.ts @@ -32,8 +32,7 @@ import { TriggerContextMapping, TriggerId } from '../types'; */ export interface Trigger { /** - * Unique name of the trigger as identified in `ui_actions` plugin trigger - * registry, such as "SELECT_RANGE_TRIGGER" or "VALUE_CLICK_TRIGGER". + * Unique name of the trigger as identified in `ui_actions` plugin trigger registry. */ id: ID; diff --git a/src/plugins/ui_actions/public/triggers/trigger_contract.ts b/src/plugins/ui_actions/public/triggers/trigger_contract.ts index 04a75cb3a53d08..7e7fba0ba80d3a 100644 --- a/src/plugins/ui_actions/public/triggers/trigger_contract.ts +++ b/src/plugins/ui_actions/public/triggers/trigger_contract.ts @@ -25,8 +25,7 @@ import { TriggerId, TriggerContextMapping } from '../types'; */ export class TriggerContract { /** - * Unique name of the trigger as identified in `ui_actions` plugin trigger - * registry, such as "SELECT_RANGE_TRIGGER" or "VALUE_CLICK_TRIGGER". + * Unique name of the trigger as identified in `ui_actions` plugin trigger registry. */ public readonly id: T; diff --git a/src/plugins/ui_actions/public/types.ts b/src/plugins/ui_actions/public/types.ts index 0266a755be9269..62fac245514cdd 100644 --- a/src/plugins/ui_actions/public/types.ts +++ b/src/plugins/ui_actions/public/types.ts @@ -20,17 +20,12 @@ import { ActionInternal } from './actions/action_internal'; import { TriggerInternal } from './triggers/trigger_internal'; import { - SELECT_RANGE_TRIGGER, - VALUE_CLICK_TRIGGER, ROW_CLICK_TRIGGER, - APPLY_FILTER_TRIGGER, VISUALIZE_FIELD_TRIGGER, VISUALIZE_GEO_FIELD_TRIGGER, DEFAULT_TRIGGER, RowClickContext, } from './triggers'; -import type { RangeSelectContext, ValueClickContext } from '../../embeddable/public'; -import type { ApplyGlobalFilterActionContext } from '../../data/public'; export type TriggerRegistry = Map>; export type ActionRegistry = Map; @@ -49,10 +44,7 @@ export type TriggerContext = BaseContext; export interface TriggerContextMapping { [DEFAULT_TRIGGER]: TriggerContext; - [SELECT_RANGE_TRIGGER]: RangeSelectContext; - [VALUE_CLICK_TRIGGER]: ValueClickContext; [ROW_CLICK_TRIGGER]: RowClickContext; - [APPLY_FILTER_TRIGGER]: ApplyGlobalFilterActionContext; [VISUALIZE_FIELD_TRIGGER]: VisualizeFieldContext; [VISUALIZE_GEO_FIELD_TRIGGER]: VisualizeFieldContext; } diff --git a/src/plugins/vis_type_table/public/components/table_visualization.tsx b/src/plugins/vis_type_table/public/components/table_visualization.tsx index 2d38acc57519f2..a1e4fad719dc42 100644 --- a/src/plugins/vis_type_table/public/components/table_visualization.tsx +++ b/src/plugins/vis_type_table/public/components/table_visualization.tsx @@ -23,6 +23,7 @@ import classNames from 'classnames'; import { CoreStart } from 'kibana/public'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import type { PersistedState } from 'src/plugins/visualizations/public'; import { KibanaContextProvider } from '../../../kibana_react/public'; import { TableVisConfig } from '../types'; import { TableContext } from '../table_vis_response_handler'; @@ -47,7 +48,7 @@ const TableVisualizationComponent = ({ handlers.done(); }, [handlers]); - const uiStateProps = useUiState(handlers.uiState); + const uiStateProps = useUiState(handlers.uiState as PersistedState); const className = classNames('tbvChart', { // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/src/plugins/vis_type_table/public/utils/use/use_ui_state.ts b/src/plugins/vis_type_table/public/utils/use/use_ui_state.ts index 68bad16972ec24..cc43fc6bcb582d 100644 --- a/src/plugins/vis_type_table/public/utils/use/use_ui_state.ts +++ b/src/plugins/vis_type_table/public/utils/use/use_ui_state.ts @@ -19,7 +19,7 @@ import { debounce, isEqual } from 'lodash'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import type { PersistedState } from 'src/plugins/visualizations/public'; import { ColumnWidthData, TableVisUiState, TableVisUseUiStateProps } from '../../types'; @@ -28,9 +28,7 @@ const defaultSort = { direction: null, }; -export const useUiState = ( - uiState: IInterpreterRenderHandlers['uiState'] -): TableVisUseUiStateProps => { +export const useUiState = (uiState: PersistedState): TableVisUseUiStateProps => { const [sort, setSortState] = useState( uiState?.get('vis.params.sort') || defaultSort ); diff --git a/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx b/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx index 67ed487d293783..dec169c59c6ef7 100644 --- a/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx +++ b/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx @@ -21,6 +21,7 @@ import React, { lazy } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { IUiSettingsClient } from 'kibana/public'; +import type { PersistedState } from '../../visualizations/public'; import { VisualizationContainer } from '../../visualizations/public'; import { ExpressionRenderDefinition } from '../../expressions/common/expression_renderers'; import { TimeseriesRenderValue, TimeseriesVisParams } from './metrics_fn'; @@ -63,7 +64,7 @@ export const getTimeseriesVisRenderer: (deps: { handlers={handlers} model={config.visParams} visData={config.visData as TimeseriesVisData} - uiState={handlers.uiState!} + uiState={handlers.uiState! as PersistedState} /> , domNode diff --git a/src/plugins/vis_type_timeseries/server/routes/fields.ts b/src/plugins/vis_type_timeseries/server/routes/fields.ts index a9a890845d154f..e787fd8d08a29e 100644 --- a/src/plugins/vis_type_timeseries/server/routes/fields.ts +++ b/src/plugins/vis_type_timeseries/server/routes/fields.ts @@ -39,7 +39,7 @@ export const fieldsRoutes = (framework: Framework) => { return res.customError({ body: err.output.payload, statusCode: err.output.statusCode, - headers: err.output.headers, + headers: err.output.headers as { [key: string]: string }, }); } diff --git a/src/plugins/vis_type_vislib/public/plugin.ts b/src/plugins/vis_type_vislib/public/plugin.ts index bef8ad26fb12c9..36a184d3da5072 100644 --- a/src/plugins/vis_type_vislib/public/plugin.ts +++ b/src/plugins/vis_type_vislib/public/plugin.ts @@ -24,7 +24,7 @@ import { VisualizationsSetup } from '../../visualizations/public'; import { ChartsPluginSetup } from '../../charts/public'; import { DataPublicPluginStart } from '../../data/public'; import { KibanaLegacyStart } from '../../kibana_legacy/public'; -import { CHARTS_LIBRARY } from '../../vis_type_xy/public'; +import { LEGACY_CHARTS_LIBRARY } from '../../vis_type_xy/public'; import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn'; import { createPieVisFn } from './pie_fn'; @@ -61,7 +61,7 @@ export class VisTypeVislibPlugin core: VisTypeVislibCoreSetup, { expressions, visualizations, charts }: VisTypeVislibPluginSetupDependencies ) { - if (core.uiSettings.get(CHARTS_LIBRARY)) { + if (!core.uiSettings.get(LEGACY_CHARTS_LIBRARY, true)) { // Register only non-replaced vis types convertedTypeDefinitions.forEach(visualizations.createBaseVisualization); visualizations.createBaseVisualization(pieVisTypeDefinition); diff --git a/src/plugins/vis_type_vislib/public/vis_controller.tsx b/src/plugins/vis_type_vislib/public/vis_controller.tsx index 1804d0d52ae7a6..2a32d19874c228 100644 --- a/src/plugins/vis_type_vislib/public/vis_controller.tsx +++ b/src/plugins/vis_type_vislib/public/vis_controller.tsx @@ -22,7 +22,7 @@ import React, { RefObject } from 'react'; import { mountReactNode } from '../../../core/public/utils'; import { ChartsPluginSetup } from '../../charts/public'; -import { PersistedState } from '../../visualizations/public'; +import type { PersistedState } from '../../visualizations/public'; import { IInterpreterRenderHandlers } from '../../expressions/public'; import { VisTypeVislibCoreSetup } from './plugin'; @@ -115,7 +115,7 @@ export const createVislibVisController = ( }) .addClass((legendClassName as any)[visParams.legendPosition]); - this.mountLegend(esResponse, visParams, fireEvent, uiState); + this.mountLegend(esResponse, visParams, fireEvent, uiState as PersistedState); } this.vislibVis.render(esResponse, uiState); @@ -128,7 +128,7 @@ export const createVislibVisController = ( CUSTOM_LEGEND_VIS_TYPES.includes(this.vislibVis.visConfigArgs.type) ) { this.unmountLegend?.(); - this.mountLegend(esResponse, visParams, fireEvent, uiState); + this.mountLegend(esResponse, visParams, fireEvent, uiState as PersistedState); this.vislibVis.render(esResponse, uiState); } } diff --git a/src/plugins/vis_type_vislib/public/vis_wrapper.tsx b/src/plugins/vis_type_vislib/public/vis_wrapper.tsx index 280a957396c348..b8dbd0f945c325 100644 --- a/src/plugins/vis_type_vislib/public/vis_wrapper.tsx +++ b/src/plugins/vis_type_vislib/public/vis_wrapper.tsx @@ -22,6 +22,7 @@ import { EuiResizeObserver } from '@elastic/eui'; import { debounce } from 'lodash'; import { IInterpreterRenderHandlers } from '../../expressions/public'; +import type { PersistedState } from '../../visualizations/public'; import { ChartsPluginSetup } from '../../charts/public'; import { VislibRenderValue } from './vis_type_vislib_vis_fn'; @@ -66,10 +67,12 @@ const VislibWrapper = ({ core, charts, visData, visConfig, handlers }: VislibWra useEffect(() => { if (handlers.uiState) { - handlers.uiState.on('change', updateChart); + const uiState = handlers.uiState as PersistedState; + + uiState.on('change', updateChart); return () => { - handlers.uiState?.off('change', updateChart); + uiState?.off('change', updateChart); }; } }, [handlers.uiState, updateChart]); diff --git a/src/plugins/vis_type_xy/common/index.ts b/src/plugins/vis_type_xy/common/index.ts index bf498229a1b549..edee1ea3219db6 100644 --- a/src/plugins/vis_type_xy/common/index.ts +++ b/src/plugins/vis_type_xy/common/index.ts @@ -34,4 +34,4 @@ export type ChartType = $Values; */ export type XyVisType = ChartType | 'horizontal_bar'; -export const CHARTS_LIBRARY = 'visualization:visualize:chartsLibrary'; +export const LEGACY_CHARTS_LIBRARY = 'visualization:visualize:legacyChartsLibrary'; diff --git a/src/plugins/vis_type_xy/public/components/split_chart_warning.tsx b/src/plugins/vis_type_xy/public/components/split_chart_warning.tsx index a9aa2bf24601b5..7265e70a791a3c 100644 --- a/src/plugins/vis_type_xy/public/components/split_chart_warning.tsx +++ b/src/plugins/vis_type_xy/public/components/split_chart_warning.tsx @@ -38,13 +38,13 @@ export const SplitChartWarning: FC = () => { > ), diff --git a/src/plugins/vis_type_xy/public/plugin.ts b/src/plugins/vis_type_xy/public/plugin.ts index c8812b07e59494..7425c5f7248aca 100644 --- a/src/plugins/vis_type_xy/public/plugin.ts +++ b/src/plugins/vis_type_xy/public/plugin.ts @@ -34,7 +34,7 @@ import { setDocLinks, } from './services'; import { visTypesDefinitions } from './vis_types'; -import { CHARTS_LIBRARY } from '../common'; +import { LEGACY_CHARTS_LIBRARY } from '../common'; import { xyVisRenderer } from './vis_renderer'; // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -71,7 +71,7 @@ export class VisTypeXyPlugin core: VisTypeXyCoreSetup, { expressions, visualizations, charts }: VisTypeXyPluginSetupDependencies ) { - if (core.uiSettings.get(CHARTS_LIBRARY, false)) { + if (!core.uiSettings.get(LEGACY_CHARTS_LIBRARY, true)) { setUISettings(core.uiSettings); setThemeService(charts.theme); setColorsService(charts.legacyColors); diff --git a/src/plugins/vis_type_xy/public/vis_component.tsx b/src/plugins/vis_type_xy/public/vis_component.tsx index d87500218975a5..dcf9f69cc291d8 100644 --- a/src/plugins/vis_type_xy/public/vis_component.tsx +++ b/src/plugins/vis_type_xy/public/vis_component.tsx @@ -50,6 +50,7 @@ import { ClickTriggerEvent, } from '../../charts/public'; import { Datatable, IInterpreterRenderHandlers } from '../../expressions/public'; +import type { PersistedState } from '../../visualizations/public'; import { VisParams } from './types'; import { @@ -77,7 +78,7 @@ import { export interface VisComponentProps { visParams: VisParams; visData: Datatable; - uiState: IInterpreterRenderHandlers['uiState']; + uiState: PersistedState; fireEvent: IInterpreterRenderHandlers['event']; renderComplete: IInterpreterRenderHandlers['done']; } diff --git a/src/plugins/vis_type_xy/public/vis_renderer.tsx b/src/plugins/vis_type_xy/public/vis_renderer.tsx index 4ac7c23c30893a..16463abb07631e 100644 --- a/src/plugins/vis_type_xy/public/vis_renderer.tsx +++ b/src/plugins/vis_type_xy/public/vis_renderer.tsx @@ -24,6 +24,7 @@ import { I18nProvider } from '@kbn/i18n/react'; import { ExpressionRenderDefinition } from '../../expressions/public'; import { VisualizationContainer } from '../../visualizations/public'; +import type { PersistedState } from '../../visualizations/public'; import { XyVisType } from '../common'; import { SplitChartWarning } from './components/split_chart_warning'; @@ -59,7 +60,7 @@ export const xyVisRenderer: ExpressionRenderDefinition = { visData={visData} renderComplete={handlers.done} fireEvent={handlers.event} - uiState={handlers.uiState} + uiState={handlers.uiState as PersistedState} /> diff --git a/src/plugins/vis_type_xy/public/vis_types/split_tooltip.tsx b/src/plugins/vis_type_xy/public/vis_types/split_tooltip.tsx index 2abe3e9b8cf712..84f1fd9187f4f7 100644 --- a/src/plugins/vis_type_xy/public/vis_types/split_tooltip.tsx +++ b/src/plugins/vis_type_xy/public/vis_types/split_tooltip.tsx @@ -25,10 +25,7 @@ export function SplitTooltip() { return ( charts library, - }} + defaultMessage="Split chart aggregation is not supported with the new charts library. Please enable the legacy charts library advanced setting to use split chart aggregation." /> ); } diff --git a/src/plugins/vis_type_xy/server/plugin.ts b/src/plugins/vis_type_xy/server/plugin.ts index 51d741f9374fe7..b5999535064aaa 100644 --- a/src/plugins/vis_type_xy/server/plugin.ts +++ b/src/plugins/vis_type_xy/server/plugin.ts @@ -22,21 +22,21 @@ import { schema } from '@kbn/config-schema'; import { CoreSetup, Plugin, UiSettingsParams } from 'kibana/server'; -import { CHARTS_LIBRARY } from '../common'; +import { LEGACY_CHARTS_LIBRARY } from '../common'; export const uiSettingsConfig: Record> = { // TODO: Remove this when vis_type_vislib is removed // https://github.com/elastic/kibana/issues/56143 - [CHARTS_LIBRARY]: { - name: i18n.translate('visTypeXy.advancedSettings.visualization.chartsLibrary', { - defaultMessage: 'Charts library', + [LEGACY_CHARTS_LIBRARY]: { + name: i18n.translate('visTypeXy.advancedSettings.visualization.legacyChartsLibrary.name', { + defaultMessage: 'Legacy charts library', }), - value: false, + value: true, description: i18n.translate( - 'visTypeXy.advancedSettings.visualization.chartsLibrary.description', + 'visTypeXy.advancedSettings.visualization.legacyChartsLibrary.description', { defaultMessage: - 'Enables new charts library for areas, lines and bars in visualize. Currently, does not support split chart aggregation.', + 'Enables legacy charts library for area, line and bar charts in visualize. Currently, only legacy charts library supports split chart aggregation.', } ), category: ['visualization'], diff --git a/src/plugins/visualizations/public/embeddable/events.ts b/src/plugins/visualizations/public/embeddable/events.ts index 41e52c3ac13272..7065d758bbc490 100644 --- a/src/plugins/visualizations/public/embeddable/events.ts +++ b/src/plugins/visualizations/public/embeddable/events.ts @@ -17,12 +17,9 @@ * under the License. */ -import { - APPLY_FILTER_TRIGGER, - SELECT_RANGE_TRIGGER, - VALUE_CLICK_TRIGGER, - ROW_CLICK_TRIGGER, -} from '../../../ui_actions/public'; +import { ROW_CLICK_TRIGGER } from '../../../ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../../../../plugins/data/public'; +import { SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER } from '../../../../plugins/embeddable/public'; export interface VisEventToTrigger { ['applyFilter']: typeof APPLY_FILTER_TRIGGER; diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index f7592977858d25..5661acc26fdb64 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -71,6 +71,9 @@ export interface VisualizeInput extends EmbeddableInput { }; savedVis?: SerializedVis; table?: unknown; + query?: Query; + filters?: Filter[]; + timeRange?: TimeRange; } export interface VisualizeOutput extends EmbeddableOutput { diff --git a/test/common/services/security/test_user.ts b/test/common/services/security/test_user.ts index 7183943591c884..25a36fed9c9c5e 100644 --- a/test/common/services/security/test_user.ts +++ b/test/common/services/security/test_user.ts @@ -87,11 +87,11 @@ export async function createTestUserService( }); if (browser && testSubjects && shouldRefreshBrowser) { - // accept alert if it pops up - const alert = await browser.getAlert(); - await alert?.accept(); if (await testSubjects.exists('kibanaChrome', { allowHidden: true })) { await browser.refresh(); + // accept alert if it pops up + const alert = await browser.getAlert(); + await alert?.accept(); await testSubjects.find('kibanaChrome', config.get('timeouts.find') * 10); } } diff --git a/test/functional/apps/dashboard/create_and_add_embeddables.ts b/test/functional/apps/dashboard/create_and_add_embeddables.ts index d8b8a6f91fe317..605ea26b76c6f7 100644 --- a/test/functional/apps/dashboard/create_and_add_embeddables.ts +++ b/test/functional/apps/dashboard/create_and_add_embeddables.ts @@ -81,7 +81,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('saves the saved visualization url to the app link', async () => { - await PageObjects.header.clickVisualize(); + await PageObjects.header.clickVisualize(true); const currentUrl = await browser.getCurrentUrl(); expect(currentUrl).to.contain(VisualizeConstants.EDIT_PATH); }); diff --git a/test/functional/apps/dashboard/dashboard_state.ts b/test/functional/apps/dashboard/dashboard_state.ts index 48fec4efee5db2..728787543927c7 100644 --- a/test/functional/apps/dashboard/dashboard_state.ts +++ b/test/functional/apps/dashboard/dashboard_state.ts @@ -46,16 +46,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('dashboard state', function describeIndexTests() { // Used to track flag before and after reset - let isNewChartUiEnabled = false; + let isNewChartsLibraryEnabled = false; before(async function () { - isNewChartUiEnabled = await PageObjects.visChart.isNewChartUiEnabled(); + isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled(); await PageObjects.dashboard.initTests(); await PageObjects.dashboard.preserveCrossAppState(); - if (isNewChartUiEnabled) { + if (isNewChartsLibraryEnabled) { await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': true, + 'visualization:visualize:legacyChartsLibrary': false, }); await browser.refresh(); } @@ -73,12 +73,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const visName = await PageObjects.visChart.getExpectedValue( AREA_CHART_VIS_NAME, - `${AREA_CHART_VIS_NAME} - chartsLibrary` + `${AREA_CHART_VIS_NAME} - new charts library` ); await dashboardAddPanel.addVisualization(visName); const dashboarName = await PageObjects.visChart.getExpectedValue( 'Overridden colors', - 'Overridden colors - chartsLibrary' + 'Overridden colors - new charts library' ); await PageObjects.dashboard.saveDashboard(dashboarName); @@ -93,7 +93,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.loadSavedDashboard(dashboarName); - if (await PageObjects.visChart.isNewChartUiEnabled()) { + if (await PageObjects.visChart.isNewChartsLibraryEnabled()) { await elasticChart.setNewChartUiDebugFlag(); await queryBar.submitQuery(); } diff --git a/test/functional/apps/dashboard/dashboard_time_picker.ts b/test/functional/apps/dashboard/dashboard_time_picker.ts index 274a4355e26e23..c759edd638260a 100644 --- a/test/functional/apps/dashboard/dashboard_time_picker.ts +++ b/test/functional/apps/dashboard/dashboard_time_picker.ts @@ -40,6 +40,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async () => { await kibanaServer.uiSettings.replace({}); await browser.refresh(); + const alert = await browser.getAlert(); + await alert?.accept(); }); it('Visualization updated when time picker changes', async () => { @@ -88,6 +90,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { `)`; log.debug('go to url' + `${kibanaBaseUrl}#${urlQuery}`); await browser.get(`${kibanaBaseUrl}#${urlQuery}`, true); + const alert = await browser.getAlert(); + await alert?.accept(); await PageObjects.header.waitUntilLoadingHasFinished(); const time = await PageObjects.timePicker.getTimeConfig(); const refresh = await PageObjects.timePicker.getRefreshConfig(); @@ -99,6 +103,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('Timepicker respects dateFormat from UI settings', async () => { await kibanaServer.uiSettings.replace({ dateFormat: 'YYYY-MM-DD HH:mm:ss.SSS' }); await browser.refresh(); + const alert = await browser.getAlert(); + await alert?.accept(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.addVisualizations([PIE_CHART_VIS_NAME]); diff --git a/test/functional/apps/dashboard/index.ts b/test/functional/apps/dashboard/index.ts index f2dba4785ea05b..6fb5f874022a03 100644 --- a/test/functional/apps/dashboard/index.ts +++ b/test/functional/apps/dashboard/index.ts @@ -126,7 +126,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { before(async () => { await loadLogstash(); await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': true, + 'visualization:visualize:legacyChartsLibrary': false, }); await browser.refresh(); }); @@ -134,7 +134,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { after(async () => { await unloadLogstash(); await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': false, + 'visualization:visualize:legacyChartsLibrary': true, }); await browser.refresh(); }); diff --git a/test/functional/apps/dashboard/panel_context_menu.ts b/test/functional/apps/dashboard/panel_context_menu.ts index 0b9e873f46151b..bd6756835af319 100644 --- a/test/functional/apps/dashboard/panel_context_menu.ts +++ b/test/functional/apps/dashboard/panel_context_menu.ts @@ -110,7 +110,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const searchName = 'my search'; before(async () => { - await PageObjects.header.clickDiscover(); + await PageObjects.header.clickDiscover(true); await PageObjects.discover.clickNewSearchButton(); await dashboardVisualizations.createSavedSearch({ name: searchName, fields: ['bytes'] }); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/test/functional/apps/dashboard/panel_replacing.ts b/test/functional/apps/dashboard/panel_replacing.ts index 6bf3dbbe47b1d1..c9ecf42dbc8e8a 100644 --- a/test/functional/apps/dashboard/panel_replacing.ts +++ b/test/functional/apps/dashboard/panel_replacing.ts @@ -70,6 +70,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('replaced panel persisted correctly when dashboard is hard refreshed', async () => { const currentUrl = await browser.getCurrentUrl(); await browser.get(currentUrl, true); + const alert = await browser.getAlert(); + await alert?.accept(); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); const panelTitles = await PageObjects.dashboard.getPanelTitles(); diff --git a/test/functional/apps/discover/_shared_links.ts b/test/functional/apps/discover/_shared_links.ts index a676878382865c..51ea5f997e859d 100644 --- a/test/functional/apps/discover/_shared_links.ts +++ b/test/functional/apps/discover/_shared_links.ts @@ -90,7 +90,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ":(from:'2015-09-19T06:31:44.000Z',to:'2015-09" + "-23T18:31:44.000Z'))&_a=(columns:!(_source),filters:!(),index:'logstash-" + "*',interval:auto,query:(language:kuery,query:'')" + - ',sort:!())'; + ",sort:!(!('@timestamp',desc)))"; const actualUrl = await PageObjects.share.getSharedUrl(); // strip the timestamp out of each URL expect(actualUrl.replace(/_t=\d{13}/, '_t=TIMESTAMP')).to.be( diff --git a/test/functional/apps/getting_started/_shakespeare.ts b/test/functional/apps/getting_started/_shakespeare.ts index fa7d932aca1e86..09fdff9977b5ed 100644 --- a/test/functional/apps/getting_started/_shakespeare.ts +++ b/test/functional/apps/getting_started/_shakespeare.ts @@ -49,22 +49,22 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // order they are added. let aggIndex = 1; // Used to track flag before and after reset - let isNewChartUiEnabled = false; + let isNewChartsLibraryEnabled = false; before(async function () { log.debug( 'Load empty_kibana and Shakespeare Getting Started data\n' + 'https://www.elastic.co/guide/en/kibana/current/tutorial-load-dataset.html' ); - isNewChartUiEnabled = await PageObjects.visChart.isNewChartUiEnabled(); + isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled(); await security.testUser.setRoles(['kibana_admin', 'test_shakespeare_reader']); await esArchiver.load('empty_kibana', { skipExisting: true }); log.debug('Load shakespeare data'); await esArchiver.loadIfNeeded('getting_started/shakespeare'); - if (isNewChartUiEnabled) { + if (isNewChartsLibraryEnabled) { await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': true, + 'visualization:visualize:legacyChartsLibrary': false, }); await browser.refresh(); } diff --git a/test/functional/apps/getting_started/index.ts b/test/functional/apps/getting_started/index.ts index 4cef16a47571ef..b832c797adac60 100644 --- a/test/functional/apps/getting_started/index.ts +++ b/test/functional/apps/getting_started/index.ts @@ -31,17 +31,17 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { }); // TODO: Remove when vislib is removed - describe('chartsLibrary', function () { + describe('new charts library', function () { before(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': true, + 'visualization:visualize:legacyChartsLibrary': false, }); await browser.refresh(); }); after(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': false, + 'visualization:visualize:legacyChartsLibrary': true, }); await browser.refresh(); }); diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index ad69ad5ab41cd2..94b0c5b6c8a277 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -43,19 +43,19 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { }); // TODO: Remove when vislib is removed - describe('chartsLibrary', function () { + describe('new charts library', function () { this.tags('ciGroup7'); before(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': true, + 'visualization:visualize:legacyChartsLibrary': false, }); await browser.refresh(); }); after(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': false, + 'visualization:visualize:legacyChartsLibrary': true, }); await browser.refresh(); }); diff --git a/test/functional/page_objects/header_page.ts b/test/functional/page_objects/header_page.ts index d69a01ab6bb264..5a892bb4f6ca3b 100644 --- a/test/functional/page_objects/header_page.ts +++ b/test/functional/page_objects/header_page.ts @@ -31,14 +31,16 @@ export function HeaderPageProvider({ getService, getPageObjects }: FtrProviderCo const defaultFindTimeout = config.get('timeouts.find'); class HeaderPage { - public async clickDiscover() { + public async clickDiscover(ignoreAppLeaveWarning = false) { await appsMenu.clickLink('Discover', { category: 'kibana' }); + await this.onAppLeaveWarning(ignoreAppLeaveWarning); await PageObjects.common.waitForTopNavToBeVisible(); await this.awaitGlobalLoadingIndicatorHidden(); } - public async clickVisualize() { + public async clickVisualize(ignoreAppLeaveWarning = false) { await appsMenu.clickLink('Visualize', { category: 'kibana' }); + await this.onAppLeaveWarning(ignoreAppLeaveWarning); await this.awaitGlobalLoadingIndicatorHidden(); await retry.waitFor('first breadcrumb to be "Visualize"', async () => { const firstBreadcrumb = await globalNav.getFirstBreadcrumb(); @@ -95,6 +97,17 @@ export function HeaderPageProvider({ getService, getPageObjects }: FtrProviderCo log.debug('awaitKibanaChrome'); await testSubjects.find('kibanaChrome', defaultFindTimeout * 10); } + + public async onAppLeaveWarning(ignoreWarning = false) { + await retry.try(async () => { + const warning = await testSubjects.exists('confirmModalTitleText'); + if (warning) { + await testSubjects.click( + ignoreWarning ? 'confirmModalConfirmButton' : 'confirmModalCancelButton' + ); + } + }); + } } return new HeaderPage(); diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts index 2ad7c77a58ca94..41af5a4c47e783 100644 --- a/test/functional/page_objects/visualize_chart_page.ts +++ b/test/functional/page_objects/visualize_chart_page.ts @@ -41,22 +41,23 @@ export function VisualizeChartPageProvider({ getService, getPageObjects }: FtrPr } /** - * Is chartsLibrary advanced setting enabled + * Is new charts library advanced setting enabled */ - public async isNewChartUiEnabled(): Promise { - const enabled = - Boolean(await kibanaServer.uiSettings.get('visualization:visualize:chartsLibrary')) ?? - false; - log.debug(`-- isNewChartUiEnabled = ${enabled}`); + public async isNewChartsLibraryEnabled(): Promise { + const legacyChartsLibrary = + Boolean(await kibanaServer.uiSettings.get('visualization:visualize:legacyChartsLibrary')) ?? + true; + const enabled = !legacyChartsLibrary; + log.debug(`-- isNewChartsLibraryEnabled = ${enabled}`); return enabled; } /** - * Is chartsLibrary enabled and an area, line or histogram chart is available + * Is new charts library enabled and an area, line or histogram chart exists */ private async isVisTypeXYChart(): Promise { - const enabled = await this.isNewChartUiEnabled(); + const enabled = await this.isNewChartsLibraryEnabled(); if (!enabled) { log.debug(`-- isVisTypeXYChart = false`); diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts index d6134883332e3b..18573c5084618c 100644 --- a/test/functional/page_objects/visualize_editor_page.ts +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -74,7 +74,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } public async clickGo() { - if (await visChart.isNewChartUiEnabled()) { + if (await visChart.isNewChartsLibraryEnabled()) { await elasticChart.setNewChartUiDebugFlag(); } diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index e5bd6a0f10d825..d8329f492fe808 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -99,7 +99,7 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide } public async clickRefresh() { - if (await visChart.isNewChartUiEnabled()) { + if (await visChart.isNewChartsLibraryEnabled()) { await elasticChart.setNewChartUiDebugFlag(); } await queryBar.clickQuerySubmitButton(); diff --git a/test/functional/services/dashboard/visualizations.ts b/test/functional/services/dashboard/visualizations.ts index b35ef1e8f2f9a9..22e1769145f884 100644 --- a/test/functional/services/dashboard/visualizations.ts +++ b/test/functional/services/dashboard/visualizations.ts @@ -58,8 +58,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }: F fields?: string[]; }) { log.debug(`createSavedSearch(${name})`); - await PageObjects.header.clickDiscover(); - + await PageObjects.header.clickDiscover(true); await PageObjects.timePicker.setHistoricalDataRange(); if (query) { diff --git a/vars/workers.groovy b/vars/workers.groovy index 365c30204ebdc0..dd634f3c25a326 100644 --- a/vars/workers.groovy +++ b/vars/workers.groovy @@ -18,7 +18,7 @@ def label(size) { case 'xl-highmem': return 'docker && tests-xl-highmem' case 'xxl': - return 'docker && tests-xxl' + return 'docker && tests-xxl && gobld/machineType:custom-64-270336' } error "unknown size '${size}'" @@ -147,7 +147,7 @@ def intake(jobName, String script) { // Worker for running functional tests. Runs a setup process (e.g. the kibana build) then executes a map of closures in parallel (e.g. one for each ciGroup) def functional(name, Closure setup, Map processes) { return { - parallelProcesses(name: name, setup: setup, processes: processes, delayBetweenProcesses: 20, size: 'xl-highmem') + parallelProcesses(name: name, setup: setup, processes: processes, delayBetweenProcesses: 20, size: 'xl') } } diff --git a/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx b/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx index 134cda6f541887..cee7ee62e3210e 100644 --- a/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx +++ b/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx @@ -32,7 +32,6 @@ import { export function getAlertType(): AlertTypeModel { return { id: 'example.always-firing', - name: 'Always Fires', description: 'Alert when called', iconClass: 'bolt', documentationUrl: null, diff --git a/x-pack/examples/alerting_example/public/alert_types/astros.tsx b/x-pack/examples/alerting_example/public/alert_types/astros.tsx index 54f989b93e22f4..cb65c9f52ca3f5 100644 --- a/x-pack/examples/alerting_example/public/alert_types/astros.tsx +++ b/x-pack/examples/alerting_example/public/alert_types/astros.tsx @@ -44,7 +44,6 @@ function isValueInEnum(enumeratin: Record, value: any): boolean { export function getAlertType(): AlertTypeModel { return { id: 'example.people-in-space', - name: 'People Are In Space Right Now', description: 'Alert when people are in space right now', iconClass: 'globe', documentationUrl: null, diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx index 48b64a1c845883..27c63aba791dd3 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx @@ -18,10 +18,12 @@ import { EuiFlexGroup, } from '@elastic/eui'; import { SampleMlJob, SampleApp1ClickContext } from '../../triggers'; -import { EmbeddableRoot } from '../../../../../../src/plugins/embeddable/public'; +import { + EmbeddableRoot, + VALUE_CLICK_TRIGGER, +} from '../../../../../../src/plugins/embeddable/public'; import { ButtonEmbeddable } from '../../embeddables/button_embeddable'; import { useUiActions } from '../../context'; -import { VALUE_CLICK_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; export const job: SampleMlJob = { job_id: '123', diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_drilldown/index.tsx index a7324f55301307..50ad350cd90b90 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_drilldown/index.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_drilldown/index.tsx @@ -8,12 +8,12 @@ import React from 'react'; import { EuiFormRow, EuiFieldText } from '@elastic/eui'; import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public'; import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../plugins/ui_actions_enhanced/public'; -import { ChartActionContext } from '../../../../../../src/plugins/embeddable/public'; -import { CollectConfigProps } from '../../../../../../src/plugins/kibana_utils/public'; import { + ChartActionContext, SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, -} from '../../../../../../src/plugins/ui_actions/public'; +} from '../../../../../../src/plugins/embeddable/public'; +import { CollectConfigProps } from '../../../../../../src/plugins/kibana_utils/public'; export type ActionContext = ChartActionContext; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_only_range_select_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_only_range_select_drilldown/index.tsx index 24385bd6baa42f..4e5b3187af42b5 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_only_range_select_drilldown/index.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_only_range_select_drilldown/index.tsx @@ -8,9 +8,11 @@ import React from 'react'; import { EuiFormRow, EuiFieldText } from '@elastic/eui'; import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public'; import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../plugins/ui_actions_enhanced/public'; -import { RangeSelectContext } from '../../../../../../src/plugins/embeddable/public'; +import { + RangeSelectContext, + SELECT_RANGE_TRIGGER, +} from '../../../../../../src/plugins/embeddable/public'; import { CollectConfigProps } from '../../../../../../src/plugins/kibana_utils/public'; -import { SELECT_RANGE_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; import { BaseActionFactoryContext } from '../../../../../plugins/ui_actions_enhanced/public/dynamic_actions'; export type Config = { diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/drilldown.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/drilldown.tsx index 9cda534a340d66..2f161efe6f3889 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/drilldown.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/drilldown.tsx @@ -13,7 +13,7 @@ import { CollectConfigContainer } from './collect_config_container'; import { SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN } from './constants'; import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../plugins/ui_actions_enhanced/public'; import { txtGoToDiscover } from './i18n'; -import { APPLY_FILTER_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../../../../../../src/plugins/data/public'; const isOutputWithIndexPatterns = ( output: unknown diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/types.ts b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/types.ts index f0497780430d4a..7c8915c3f143fd 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/types.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/types.ts @@ -6,8 +6,9 @@ import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../../src/plugins/kibana_utils/public'; import { ApplyGlobalFilterActionContext } from '../../../../../../src/plugins/data/public'; +import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; -export type ActionContext = ApplyGlobalFilterActionContext; +export type ActionContext = ApplyGlobalFilterActionContext & { embeddable: IEmbeddable }; export type Config = { /** diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/button_embeddable.ts b/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/button_embeddable.ts index fcd0c9b94c988c..e58ab5b8ea9d25 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/button_embeddable.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/button_embeddable.ts @@ -7,9 +7,12 @@ import { createElement } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { AdvancedUiActionsStart } from '../../../../../plugins/ui_actions_enhanced/public'; -import { Embeddable, EmbeddableInput } from '../../../../../../src/plugins/embeddable/public'; +import { + Embeddable, + EmbeddableInput, + VALUE_CLICK_TRIGGER, +} from '../../../../../../src/plugins/embeddable/public'; import { ButtonEmbeddableComponent } from './button_embeddable_component'; -import { VALUE_CLICK_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; export const BUTTON_EMBEDDABLE = 'BUTTON_EMBEDDABLE'; diff --git a/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts b/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts index 6dc2cb3163b1f9..78d771aec13e04 100644 --- a/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts +++ b/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts @@ -14,9 +14,6 @@ export function registerApmAlerts( ) { alertTypeRegistry.register({ id: AlertType.ErrorCount, - name: i18n.translate('xpack.apm.alertTypes.errorCount', { - defaultMessage: 'Error count threshold', - }), description: i18n.translate('xpack.apm.alertTypes.errorCount.description', { defaultMessage: 'Alert when the number of errors in a service exceeds a defined threshold.', @@ -45,9 +42,6 @@ export function registerApmAlerts( alertTypeRegistry.register({ id: AlertType.TransactionDuration, - name: i18n.translate('xpack.apm.alertTypes.transactionDuration', { - defaultMessage: 'Transaction duration threshold', - }), description: i18n.translate( 'xpack.apm.alertTypes.transactionDuration.description', { @@ -82,9 +76,6 @@ export function registerApmAlerts( alertTypeRegistry.register({ id: AlertType.TransactionErrorRate, - name: i18n.translate('xpack.apm.alertTypes.transactionErrorRate', { - defaultMessage: 'Transaction error rate threshold', - }), description: i18n.translate( 'xpack.apm.alertTypes.transactionErrorRate.description', { @@ -119,9 +110,6 @@ export function registerApmAlerts( alertTypeRegistry.register({ id: AlertType.TransactionDurationAnomaly, - name: i18n.translate('xpack.apm.alertTypes.transactionDurationAnomaly', { - defaultMessage: 'Transaction duration anomaly', - }), description: i18n.translate( 'xpack.apm.alertTypes.transactionDurationAnomaly.description', { diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx index 0187ecd927e658..b0ef28fbb7b0d2 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx @@ -13,7 +13,7 @@ import { import { Transaction } from '../../../../../../../typings/es_schemas/ui/transaction'; import { TransactionDetailLink } from '../../../../../shared/Links/apm/TransactionDetailLink'; import { StickyProperties } from '../../../../../shared/StickyProperties'; -import { TransactionOverviewLink } from '../../../../../shared/Links/apm/TransactionOverviewLink'; +import { ServiceOrTransactionsOverviewLink } from '../../../../../shared/Links/apm/service_transactions_overview'; interface Props { transaction?: Transaction; @@ -31,9 +31,11 @@ export function FlyoutTopLevelProperties({ transaction }: Props) { }), fieldName: SERVICE_NAME, val: ( - + {transaction.service.name} - + ), width: '25%', }, diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx index c068fee3cd6c33..ca5b4938ff42ee 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx @@ -15,7 +15,7 @@ import { import { NOT_AVAILABLE_LABEL } from '../../../../../../../../common/i18n'; import { Span } from '../../../../../../../../typings/es_schemas/ui/span'; import { StickyProperties } from '../../../../../../shared/StickyProperties'; -import { TransactionOverviewLink } from '../../../../../../shared/Links/apm/TransactionOverviewLink'; +import { ServiceOrTransactionsOverviewLink } from '../../../../../../shared/Links/apm/service_transactions_overview'; import { TransactionDetailLink } from '../../../../../../shared/Links/apm/TransactionDetailLink'; interface Props { @@ -33,9 +33,11 @@ export function StickySpanProperties({ span, transaction }: Props) { }), fieldName: SERVICE_NAME, val: ( - + {transaction.service.name} - + ), width: '25%', }, diff --git a/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx b/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx index ae0dd85b6a8b55..961320baa6a4ec 100644 --- a/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx +++ b/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx @@ -15,7 +15,7 @@ import { useMetricOverviewHref } from '../../shared/Links/apm/MetricOverviewLink import { useServiceMapHref } from '../../shared/Links/apm/ServiceMapLink'; import { useServiceNodeOverviewHref } from '../../shared/Links/apm/ServiceNodeOverviewLink'; import { useServiceOverviewHref } from '../../shared/Links/apm/service_overview_link'; -import { useTransactionOverviewHref } from '../../shared/Links/apm/TransactionOverviewLink'; +import { useServiceOrTransactionsOverviewHref } from '../../shared/Links/apm/service_transactions_overview'; import { MainTabs } from '../../shared/main_tabs'; import { ErrorGroupOverview } from '../ErrorGroupOverview'; import { ServiceMap } from '../ServiceMap'; @@ -60,7 +60,7 @@ export function ServiceDetailTabs({ serviceName, tab }: Props) { const transactionsTab = { key: 'transactions', - href: useTransactionOverviewHref(serviceName), + href: useServiceOrTransactionsOverviewHref(serviceName), text: i18n.translate('xpack.apm.serviceDetails.transactionsTabLabel', { defaultMessage: 'Transactions', }), diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx index a4c93f95dc53d7..157d3ecc738a1a 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx @@ -21,7 +21,7 @@ import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { fontSizes, px, truncate, unit } from '../../../../style/variables'; import { ManagedTable, ITableColumn } from '../../../shared/ManagedTable'; import { EnvironmentBadge } from '../../../shared/EnvironmentBadge'; -import { TransactionOverviewLink } from '../../../shared/Links/apm/TransactionOverviewLink'; +import { ServiceOrTransactionsOverviewLink } from '../../../shared/Links/apm/service_transactions_overview'; import { AgentIcon } from '../../../shared/AgentIcon'; import { HealthBadge } from './HealthBadge'; import { ServiceListMetric } from './ServiceListMetric'; @@ -39,7 +39,7 @@ function formatString(value?: string | null) { return value || NOT_AVAILABLE_LABEL; } -const AppLink = styled(TransactionOverviewLink)` +const AppLink = styled(ServiceOrTransactionsOverviewLink)` font-size: ${fontSizes.large}; ${truncate('100%')}; `; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx index ed4f3277a4a045..ae297b840ebc88 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx @@ -4,13 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexItem } from '@elastic/eui'; -import { EuiInMemoryTable } from '@elastic/eui'; -import { EuiTitle } from '@elastic/eui'; -import { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiFlexGroup } from '@elastic/eui'; +import { + EuiBasicTableColumn, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiTitle, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; import { asDuration, asPercent, @@ -18,20 +21,18 @@ import { } from '../../../../../common/utils/formatters'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ServiceDependencyItem } from '../../../../../server/lib/services/get_service_dependencies'; -import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; -import { ServiceMapLink } from '../../../shared/Links/apm/ServiceMapLink'; -import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; -import { TableLinkFlexItem } from '../table_link_flex_item'; +import { px, unit } from '../../../../style/variables'; import { AgentIcon } from '../../../shared/AgentIcon'; -import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; import { SparkPlot } from '../../../shared/charts/spark_plot'; -import { px, unit } from '../../../../style/variables'; import { ImpactBar } from '../../../shared/ImpactBar'; +import { ServiceMapLink } from '../../../shared/Links/apm/ServiceMapLink'; import { ServiceOverviewLink } from '../../../shared/Links/apm/service_overview_link'; import { SpanIcon } from '../../../shared/span_icon'; +import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; +import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; interface Props { @@ -192,8 +193,8 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { return ( - - + +

{i18n.translate( @@ -205,7 +206,7 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) {

- + {i18n.translate( 'xpack.apm.serviceOverview.dependenciesTableLinkText', @@ -214,7 +215,7 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { } )} - +
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index da74a6fc0004d5..d14ef648c22d3d 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -25,7 +25,6 @@ import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; import { TimestampTooltip } from '../../../shared/TimestampTooltip'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; -import { TableLinkFlexItem } from '../table_link_flex_item'; interface Props { serviceName: string; @@ -195,8 +194,8 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { return ( - - + +

{i18n.translate('xpack.apm.serviceOverview.errorsTableTitle', { @@ -205,13 +204,13 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) {

- + {i18n.translate('xpack.apm.serviceOverview.errorsTableLinkText', { defaultMessage: 'View errors', })} - +
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx index 6345d546c716fd..4b262f1f51319a 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx @@ -20,6 +20,7 @@ import { asPercent, asTransactionRate, } from '../../../../../common/utils/formatters'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useLatencyAggregationType } from '../../../../hooks/use_latency_Aggregation_type'; @@ -28,13 +29,11 @@ import { callApmApi, } from '../../../../services/rest/createCallApmApi'; import { px, unit } from '../../../../style/variables'; -import { TransactionDetailLink } from '../../../shared/Links/apm/TransactionDetailLink'; -import { TransactionOverviewLink } from '../../../shared/Links/apm/TransactionOverviewLink'; -import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; -import { TableLinkFlexItem } from '../table_link_flex_item'; import { SparkPlot } from '../../../shared/charts/spark_plot'; import { ImpactBar } from '../../../shared/ImpactBar'; -import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { TransactionDetailLink } from '../../../shared/Links/apm/TransactionDetailLink'; +import { TransactionOverviewLink } from '../../../shared/Links/apm/transaction_overview_ink'; +import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; @@ -270,7 +269,7 @@ export function ServiceOverviewTransactionsTable(props: Props) { - +

{i18n.translate( @@ -282,7 +281,7 @@ export function ServiceOverviewTransactionsTable(props: Props) {

- + {i18n.translate( 'xpack.apm.serviceOverview.transactionsTableLinkText', @@ -291,7 +290,7 @@ export function ServiceOverviewTransactionsTable(props: Props) { } )} - +
diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/service_transactions_overview.tsx similarity index 86% rename from x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx rename to x-pack/plugins/apm/public/components/shared/Links/apm/service_transactions_overview.tsx index 1d99b82a67326a..24a78e5d64749a 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/service_transactions_overview.tsx @@ -18,7 +18,7 @@ const persistedFilters: Array = [ 'latencyAggregationType', ]; -export function useTransactionOverviewHref(serviceName: string) { +export function useServiceOrTransactionsOverviewHref(serviceName: string) { return useAPMHref(`/services/${serviceName}/transactions`, persistedFilters); } @@ -26,7 +26,10 @@ interface Props extends APMLinkExtendProps { serviceName: string; } -export function TransactionOverviewLink({ serviceName, ...rest }: Props) { +export function ServiceOrTransactionsOverviewLink({ + serviceName, + ...rest +}: Props) { const { urlParams } = useUrlParams(); return ( diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/transaction_overview_ink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/transaction_overview_ink.tsx new file mode 100644 index 00000000000000..d2978b3c02d532 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/transaction_overview_ink.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { pickKeys } from '../../../../../common/utils/pick_keys'; +import { APMLink, APMLinkExtendProps } from './APMLink'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { APMQueryParams } from '../url_helpers'; + +interface Props extends APMLinkExtendProps { + serviceName: string; +} + +const persistedFilters: Array = [ + 'latencyAggregationType', +]; + +export function TransactionOverviewLink({ serviceName, ...rest }: Props) { + const { urlParams } = useUrlParams(); + + return ( + + ); +} diff --git a/x-pack/plugins/apm/server/routes/create_api/index.ts b/x-pack/plugins/apm/server/routes/create_api/index.ts index ef445617e9295b..94711cf76c145f 100644 --- a/x-pack/plugins/apm/server/routes/create_api/index.ts +++ b/x-pack/plugins/apm/server/routes/create_api/index.ts @@ -140,7 +140,7 @@ export function createApi() { } function convertBoomToKibanaResponse( - error: Boom, + error: Boom.Boom, response: KibanaResponseFactory ) { const opts = { body: error.message }; diff --git a/x-pack/plugins/case/common/api/connectors/mappings.ts b/x-pack/plugins/case/common/api/connectors/mappings.ts index 3e8baf0af28343..b91f9d69e85e26 100644 --- a/x-pack/plugins/case/common/api/connectors/mappings.ts +++ b/x-pack/plugins/case/common/api/connectors/mappings.ts @@ -180,6 +180,8 @@ export const PostPushRequestRt = rt.type({ params: ServiceConnectorCaseParamsRt, }); +export type PostPushRequest = rt.TypeOf; + export interface SimpleComment { comment: string; commentId: string; diff --git a/x-pack/plugins/case/server/client/configure/get_fields.test.ts b/x-pack/plugins/case/server/client/configure/get_fields.test.ts new file mode 100644 index 00000000000000..b465d916b2292e --- /dev/null +++ b/x-pack/plugins/case/server/client/configure/get_fields.test.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ConnectorTypes } from '../../../common/api'; + +import { createMockSavedObjectsRepository, mockCaseMappings } from '../../routes/api/__fixtures__'; +import { createCaseClientWithMockSavedObjectsClient } from '../mocks'; +import { actionsClientMock } from '../../../../actions/server/actions_client.mock'; +import { actionsErrResponse, mappings, mockGetFieldsResponse } from './mock'; +describe('get_fields', () => { + const execute = jest.fn().mockReturnValue(mockGetFieldsResponse); + const actionsMock = { ...actionsClientMock.create(), execute }; + beforeEach(async () => { + jest.clearAllMocks(); + }); + + describe('happy path', () => { + test('it gets fields', async () => { + const savedObjectsClient = createMockSavedObjectsRepository({ + caseMappingsSavedObject: mockCaseMappings, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + const res = await caseClient.client.getFields({ + actionsClient: actionsMock, + connectorType: ConnectorTypes.jira, + connectorId: '123', + }); + expect(res).toEqual({ + fields: [ + { id: 'summary', name: 'Summary', required: true, type: 'text' }, + { id: 'description', name: 'Description', required: false, type: 'text' }, + ], + defaultMappings: mappings[ConnectorTypes.jira], + }); + }); + }); + + describe('unhappy path', () => { + test('it throws error', async () => { + const savedObjectsClient = createMockSavedObjectsRepository({ + caseMappingsSavedObject: mockCaseMappings, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + await caseClient.client + .getFields({ + actionsClient: { ...actionsMock, execute: jest.fn().mockReturnValue(actionsErrResponse) }, + connectorType: ConnectorTypes.jira, + connectorId: '123', + }) + .catch((e) => { + expect(e).not.toBeNull(); + expect(e.isBoom).toBe(true); + expect(e.output.statusCode).toBe(424); + }); + }); + }); +}); diff --git a/x-pack/plugins/case/server/client/configure/get_mappings.test.ts b/x-pack/plugins/case/server/client/configure/get_mappings.test.ts new file mode 100644 index 00000000000000..e68db5cde940bb --- /dev/null +++ b/x-pack/plugins/case/server/client/configure/get_mappings.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ConnectorTypes } from '../../../common/api'; + +import { createMockSavedObjectsRepository, mockCaseMappings } from '../../routes/api/__fixtures__'; +import { createCaseClientWithMockSavedObjectsClient } from '../mocks'; +import { actionsClientMock } from '../../../../actions/server/actions_client.mock'; +import { mappings, mockGetFieldsResponse } from './mock'; + +describe('get_mappings', () => { + const execute = jest.fn().mockReturnValue(mockGetFieldsResponse); + const actionsMock = { ...actionsClientMock.create(), execute }; + beforeEach(async () => { + jest.restoreAllMocks(); + const spyOnDate = jest.spyOn(global, 'Date') as jest.SpyInstance<{}, []>; + spyOnDate.mockImplementation(() => ({ + toISOString: jest.fn().mockReturnValue('2019-11-25T21:54:48.952Z'), + })); + }); + + describe('happy path', () => { + test('it gets existing mappings', async () => { + const savedObjectsClient = createMockSavedObjectsRepository({ + caseMappingsSavedObject: mockCaseMappings, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + const res = await caseClient.client.getMappings({ + actionsClient: actionsMock, + caseClient: caseClient.client, + connectorType: ConnectorTypes.jira, + connectorId: '123', + }); + + expect(res).toEqual(mappings[ConnectorTypes.jira]); + }); + test('it creates new mappings', async () => { + const savedObjectsClient = createMockSavedObjectsRepository({ + caseMappingsSavedObject: [], + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + const res = await caseClient.client.getMappings({ + actionsClient: actionsMock, + caseClient: caseClient.client, + connectorType: ConnectorTypes.jira, + connectorId: '123', + }); + + expect(res).toEqual(mappings[ConnectorTypes.jira]); + }); + }); +}); diff --git a/x-pack/plugins/case/server/client/configure/mock.ts b/x-pack/plugins/case/server/client/configure/mock.ts new file mode 100644 index 00000000000000..bb57755250ba2c --- /dev/null +++ b/x-pack/plugins/case/server/client/configure/mock.ts @@ -0,0 +1,625 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + ConnectorField, + ConnectorMappingsAttributes, + ConnectorTypes, +} from '../../../common/api/connectors'; +import { + JiraGetFieldsResponse, + ResilientGetFieldsResponse, + ServiceNowGetFieldsResponse, +} from './utils.test'; +interface TestMappings { + [key: string]: ConnectorMappingsAttributes[]; +} +export const mappings: TestMappings = { + [ConnectorTypes.jira]: [ + { + source: 'title', + target: 'summary', + action_type: 'overwrite', + }, + { + source: 'description', + target: 'description', + action_type: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + action_type: 'append', + }, + ], + [`${ConnectorTypes.jira}-alt`]: [ + { + source: 'title', + target: 'title', + action_type: 'overwrite', + }, + { + source: 'description', + target: 'description', + action_type: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + action_type: 'append', + }, + ], + [ConnectorTypes.resilient]: [ + { + source: 'title', + target: 'name', + action_type: 'overwrite', + }, + { + source: 'description', + target: 'description', + action_type: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + action_type: 'append', + }, + ], + [ConnectorTypes.servicenow]: [ + { + source: 'title', + target: 'short_description', + action_type: 'overwrite', + }, + { + source: 'description', + target: 'description', + action_type: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + action_type: 'append', + }, + ], +}; + +const jiraFields: JiraGetFieldsResponse = { + summary: { + required: true, + allowedValues: [], + defaultValue: {}, + schema: { + type: 'string', + }, + name: 'Summary', + }, + issuetype: { + required: true, + allowedValues: [ + { + self: 'https://siem-kibana.atlassian.net/rest/api/2/issuetype/10023', + id: '10023', + description: 'A problem or error.', + iconUrl: + 'https://siem-kibana.atlassian.net/secure/viewavatar?size=medium&avatarId=10303&avatarType=issuetype', + name: 'Bug', + subtask: false, + avatarId: 10303, + }, + ], + defaultValue: {}, + schema: { + type: 'issuetype', + }, + name: 'Issue Type', + }, + attachment: { + required: false, + allowedValues: [], + defaultValue: {}, + schema: { + type: 'array', + items: 'attachment', + }, + name: 'Attachment', + }, + duedate: { + required: false, + allowedValues: [], + defaultValue: {}, + schema: { + type: 'date', + }, + name: 'Due date', + }, + description: { + required: false, + allowedValues: [], + defaultValue: {}, + schema: { + type: 'string', + }, + name: 'Description', + }, + project: { + required: true, + allowedValues: [ + { + self: 'https://siem-kibana.atlassian.net/rest/api/2/project/10015', + id: '10015', + key: 'RJ2', + name: 'RJ2', + projectTypeKey: 'business', + simplified: false, + avatarUrls: { + '48x48': + 'https://siem-kibana.atlassian.net/secure/projectavatar?pid=10015&avatarId=10412', + '24x24': + 'https://siem-kibana.atlassian.net/secure/projectavatar?size=small&s=small&pid=10015&avatarId=10412', + '16x16': + 'https://siem-kibana.atlassian.net/secure/projectavatar?size=xsmall&s=xsmall&pid=10015&avatarId=10412', + '32x32': + 'https://siem-kibana.atlassian.net/secure/projectavatar?size=medium&s=medium&pid=10015&avatarId=10412', + }, + }, + ], + defaultValue: {}, + schema: { + type: 'project', + }, + name: 'Project', + }, + assignee: { + required: false, + allowedValues: [], + defaultValue: {}, + schema: { + type: 'user', + }, + name: 'Assignee', + }, + labels: { + required: false, + allowedValues: [], + defaultValue: {}, + schema: { + type: 'array', + items: 'string', + }, + name: 'Labels', + }, +}; +const resilientFields: ResilientGetFieldsResponse = [ + { input_type: 'text', name: 'addr', read_only: false, text: 'Address' }, + { + input_type: 'boolean', + name: 'alberta_health_risk_assessment', + read_only: false, + text: 'Alberta Health Risk Assessment', + }, + { input_type: 'number', name: 'hard_liability', read_only: true, text: 'Assessed Liability' }, + { input_type: 'text', name: 'city', read_only: false, text: 'City' }, + { input_type: 'select', name: 'country', read_only: false, text: 'Country/Region' }, + { input_type: 'select_owner', name: 'creator_id', read_only: true, text: 'Created By' }, + { input_type: 'select', name: 'crimestatus_id', read_only: false, text: 'Criminal Activity' }, + { input_type: 'boolean', name: 'data_encrypted', read_only: false, text: 'Data Encrypted' }, + { input_type: 'select', name: 'data_format', read_only: false, text: 'Data Format' }, + { input_type: 'datetimepicker', name: 'end_date', read_only: true, text: 'Date Closed' }, + { input_type: 'datetimepicker', name: 'create_date', read_only: true, text: 'Date Created' }, + { + input_type: 'datetimepicker', + name: 'determined_date', + read_only: false, + text: 'Date Determined', + }, + { + input_type: 'datetimepicker', + name: 'discovered_date', + read_only: false, + required: 'always', + text: 'Date Discovered', + }, + { input_type: 'datetimepicker', name: 'start_date', read_only: false, text: 'Date Occurred' }, + { input_type: 'select', name: 'exposure_dept_id', read_only: false, text: 'Department' }, + { input_type: 'textarea', name: 'description', read_only: false, text: 'Description' }, + { input_type: 'boolean', name: 'employee_involved', read_only: false, text: 'Employee Involved' }, + { input_type: 'boolean', name: 'data_contained', read_only: false, text: 'Exposure Resolved' }, + { input_type: 'select', name: 'exposure_type_id', read_only: false, text: 'Exposure Type' }, + { + input_type: 'multiselect', + name: 'gdpr_breach_circumstances', + read_only: false, + text: 'GDPR Breach Circumstances', + }, + { input_type: 'select', name: 'gdpr_breach_type', read_only: false, text: 'GDPR Breach Type' }, + { + input_type: 'textarea', + name: 'gdpr_breach_type_comment', + read_only: false, + text: 'GDPR Breach Type Comment', + }, + { input_type: 'select', name: 'gdpr_consequences', read_only: false, text: 'GDPR Consequences' }, + { + input_type: 'textarea', + name: 'gdpr_consequences_comment', + read_only: false, + text: 'GDPR Consequences Comment', + }, + { + input_type: 'select', + name: 'gdpr_final_assessment', + read_only: false, + text: 'GDPR Final Assessment', + }, + { + input_type: 'textarea', + name: 'gdpr_final_assessment_comment', + read_only: false, + text: 'GDPR Final Assessment Comment', + }, + { + input_type: 'select', + name: 'gdpr_identification', + read_only: false, + text: 'GDPR Identification', + }, + { + input_type: 'textarea', + name: 'gdpr_identification_comment', + read_only: false, + text: 'GDPR Identification Comment', + }, + { + input_type: 'select', + name: 'gdpr_personal_data', + read_only: false, + text: 'GDPR Personal Data', + }, + { + input_type: 'textarea', + name: 'gdpr_personal_data_comment', + read_only: false, + text: 'GDPR Personal Data Comment', + }, + { + input_type: 'boolean', + name: 'gdpr_subsequent_notification', + read_only: false, + text: 'GDPR Subsequent Notification', + }, + { input_type: 'number', name: 'id', read_only: true, text: 'ID' }, + { input_type: 'boolean', name: 'impact_likely', read_only: false, text: 'Impact Likely' }, + { + input_type: 'boolean', + name: 'ny_impact_likely', + read_only: false, + text: 'Impact Likely for New York', + }, + { + input_type: 'boolean', + name: 'or_impact_likely', + read_only: false, + text: 'Impact Likely for Oregon', + }, + { + input_type: 'boolean', + name: 'wa_impact_likely', + read_only: false, + text: 'Impact Likely for Washington', + }, + { input_type: 'boolean', name: 'confirmed', read_only: false, text: 'Incident Disposition' }, + { input_type: 'multiselect', name: 'incident_type_ids', read_only: false, text: 'Incident Type' }, + { + input_type: 'text', + name: 'exposure_individual_name', + read_only: false, + text: 'Individual Name', + }, + { + input_type: 'select', + name: 'harmstatus_id', + read_only: false, + text: 'Is harm/risk/misuse foreseeable?', + }, + { input_type: 'text', name: 'jurisdiction_name', read_only: false, text: 'Jurisdiction' }, + { + input_type: 'datetimepicker', + name: 'inc_last_modified_date', + read_only: true, + text: 'Last Modified', + }, + { + input_type: 'multiselect', + name: 'gdpr_lawful_data_processing_categories', + read_only: false, + text: 'Lawful Data Processing Categories', + }, + { input_type: 'multiselect_members', name: 'members', read_only: false, text: 'Members' }, + { input_type: 'text', name: 'name', read_only: false, required: 'always', text: 'Name' }, + { input_type: 'boolean', name: 'negative_pr_likely', read_only: false, text: 'Negative PR' }, + { input_type: 'datetimepicker', name: 'due_date', read_only: true, text: 'Next Due Date' }, + { + input_type: 'multiselect', + name: 'nist_attack_vectors', + read_only: false, + text: 'NIST Attack Vectors', + }, + { input_type: 'select', name: 'org_handle', read_only: true, text: 'Organization' }, + { input_type: 'select_owner', name: 'owner_id', read_only: false, text: 'Owner' }, + { input_type: 'select', name: 'phase_id', read_only: true, text: 'Phase' }, + { + input_type: 'select', + name: 'pipeda_other_factors', + read_only: false, + text: 'PIPEDA Other Factors', + }, + { + input_type: 'textarea', + name: 'pipeda_other_factors_comment', + read_only: false, + text: 'PIPEDA Other Factors Comment', + }, + { + input_type: 'select', + name: 'pipeda_overall_assessment', + read_only: false, + text: 'PIPEDA Overall Assessment', + }, + { + input_type: 'textarea', + name: 'pipeda_overall_assessment_comment', + read_only: false, + text: 'PIPEDA Overall Assessment Comment', + }, + { + input_type: 'select', + name: 'pipeda_probability_of_misuse', + read_only: false, + text: 'PIPEDA Probability of Misuse', + }, + { + input_type: 'textarea', + name: 'pipeda_probability_of_misuse_comment', + read_only: false, + text: 'PIPEDA Probability of Misuse Comment', + }, + { + input_type: 'select', + name: 'pipeda_sensitivity_of_pi', + read_only: false, + text: 'PIPEDA Sensitivity of PI', + }, + { + input_type: 'textarea', + name: 'pipeda_sensitivity_of_pi_comment', + read_only: false, + text: 'PIPEDA Sensitivity of PI Comment', + }, + { input_type: 'text', name: 'reporter', read_only: false, text: 'Reporting Individual' }, + { + input_type: 'select', + name: 'resolution_id', + read_only: false, + required: 'close', + text: 'Resolution', + }, + { + input_type: 'textarea', + name: 'resolution_summary', + read_only: false, + required: 'close', + text: 'Resolution Summary', + }, + { input_type: 'select', name: 'gdpr_harm_risk', read_only: false, text: 'Risk of Harm' }, + { input_type: 'select', name: 'severity_code', read_only: false, text: 'Severity' }, + { input_type: 'boolean', name: 'inc_training', read_only: true, text: 'Simulation' }, + { input_type: 'multiselect', name: 'data_source_ids', read_only: false, text: 'Source of Data' }, + { input_type: 'select', name: 'state', read_only: false, text: 'State' }, + { input_type: 'select', name: 'plan_status', read_only: false, text: 'Status' }, + { input_type: 'select', name: 'exposure_vendor_id', read_only: false, text: 'Vendor' }, + { + input_type: 'boolean', + name: 'data_compromised', + read_only: false, + text: 'Was personal information or personal data involved?', + }, + { + input_type: 'select', + name: 'workspace', + read_only: false, + required: 'always', + text: 'Workspace', + }, + { input_type: 'text', name: 'zip', read_only: false, text: 'Zip' }, +]; +const serviceNowFields: ServiceNowGetFieldsResponse = [ + { + column_label: 'Approval', + mandatory: 'false', + max_length: '40', + element: 'approval', + }, + { + column_label: 'Close notes', + mandatory: 'false', + max_length: '4000', + element: 'close_notes', + }, + { + column_label: 'Contact type', + mandatory: 'false', + max_length: '40', + element: 'contact_type', + }, + { + column_label: 'Correlation display', + mandatory: 'false', + max_length: '100', + element: 'correlation_display', + }, + { + column_label: 'Correlation ID', + mandatory: 'false', + max_length: '100', + element: 'correlation_id', + }, + { + column_label: 'Description', + mandatory: 'false', + max_length: '4000', + element: 'description', + }, + { + column_label: 'Number', + mandatory: 'false', + max_length: '40', + element: 'number', + }, + { + column_label: 'Short description', + mandatory: 'false', + max_length: '160', + element: 'short_description', + }, + { + column_label: 'Created by', + mandatory: 'false', + max_length: '40', + element: 'sys_created_by', + }, + { + column_label: 'Updated by', + mandatory: 'false', + max_length: '40', + element: 'sys_updated_by', + }, + { + column_label: 'Upon approval', + mandatory: 'false', + max_length: '40', + element: 'upon_approval', + }, + { + column_label: 'Upon reject', + mandatory: 'false', + max_length: '40', + element: 'upon_reject', + }, +]; +interface FormatFieldsTestData { + expected: ConnectorField[]; + fields: JiraGetFieldsResponse | ResilientGetFieldsResponse | ServiceNowGetFieldsResponse; + type: ConnectorTypes; +} +export const formatFieldsTestData: FormatFieldsTestData[] = [ + { + expected: [ + { id: 'summary', name: 'Summary', required: true, type: 'text' }, + { id: 'description', name: 'Description', required: false, type: 'text' }, + ], + fields: jiraFields, + type: ConnectorTypes.jira, + }, + { + expected: [ + { id: 'addr', name: 'Address', required: false, type: 'text' }, + { id: 'city', name: 'City', required: false, type: 'text' }, + { id: 'description', name: 'Description', required: false, type: 'textarea' }, + { + id: 'gdpr_breach_type_comment', + name: 'GDPR Breach Type Comment', + required: false, + type: 'textarea', + }, + { + id: 'gdpr_consequences_comment', + name: 'GDPR Consequences Comment', + required: false, + type: 'textarea', + }, + { + id: 'gdpr_final_assessment_comment', + name: 'GDPR Final Assessment Comment', + required: false, + type: 'textarea', + }, + { + id: 'gdpr_identification_comment', + name: 'GDPR Identification Comment', + required: false, + type: 'textarea', + }, + { + id: 'gdpr_personal_data_comment', + name: 'GDPR Personal Data Comment', + required: false, + type: 'textarea', + }, + { id: 'exposure_individual_name', name: 'Individual Name', required: false, type: 'text' }, + { id: 'jurisdiction_name', name: 'Jurisdiction', required: false, type: 'text' }, + { id: 'name', name: 'Name', required: true, type: 'text' }, + { + id: 'pipeda_other_factors_comment', + name: 'PIPEDA Other Factors Comment', + required: false, + type: 'textarea', + }, + { + id: 'pipeda_overall_assessment_comment', + name: 'PIPEDA Overall Assessment Comment', + required: false, + type: 'textarea', + }, + { + id: 'pipeda_probability_of_misuse_comment', + name: 'PIPEDA Probability of Misuse Comment', + required: false, + type: 'textarea', + }, + { + id: 'pipeda_sensitivity_of_pi_comment', + name: 'PIPEDA Sensitivity of PI Comment', + required: false, + type: 'textarea', + }, + { id: 'reporter', name: 'Reporting Individual', required: false, type: 'text' }, + { id: 'resolution_summary', name: 'Resolution Summary', required: false, type: 'textarea' }, + { id: 'zip', name: 'Zip', required: false, type: 'text' }, + ], + fields: resilientFields, + type: ConnectorTypes.resilient, + }, + { + expected: [ + { id: 'approval', name: 'Approval', required: false, type: 'text' }, + { id: 'close_notes', name: 'Close notes', required: false, type: 'textarea' }, + { id: 'contact_type', name: 'Contact type', required: false, type: 'text' }, + { id: 'correlation_display', name: 'Correlation display', required: false, type: 'text' }, + { id: 'correlation_id', name: 'Correlation ID', required: false, type: 'text' }, + { id: 'description', name: 'Description', required: false, type: 'textarea' }, + { id: 'number', name: 'Number', required: false, type: 'text' }, + { id: 'short_description', name: 'Short description', required: false, type: 'text' }, + { id: 'sys_created_by', name: 'Created by', required: false, type: 'text' }, + { id: 'sys_updated_by', name: 'Updated by', required: false, type: 'text' }, + { id: 'upon_approval', name: 'Upon approval', required: false, type: 'text' }, + { id: 'upon_reject', name: 'Upon reject', required: false, type: 'text' }, + ], + fields: serviceNowFields, + type: ConnectorTypes.servicenow, + }, +]; +export const mockGetFieldsResponse = { + status: 'ok', + data: jiraFields, + actionId: '123', +}; + +export const actionsErrResponse = { + status: 'error', + serviceMessage: 'this is an actions error', +}; diff --git a/x-pack/plugins/case/server/client/configure/utils.test.ts b/x-pack/plugins/case/server/client/configure/utils.test.ts index 91c8259cb2c555..f4f0e077425218 100644 --- a/x-pack/plugins/case/server/client/configure/utils.test.ts +++ b/x-pack/plugins/case/server/client/configure/utils.test.ts @@ -4,535 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { +export { JiraGetFieldsResponse, ResilientGetFieldsResponse, ServiceNowGetFieldsResponse, } from '../../../../actions/server/types'; -import { formatFields } from './utils'; +import { createDefaultMapping, formatFields } from './utils'; import { ConnectorTypes } from '../../../common/api/connectors'; +import { mappings, formatFieldsTestData } from './mock'; -const jiraFields: JiraGetFieldsResponse = { - summary: { - required: true, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'string', - }, - name: 'Summary', - }, - issuetype: { - required: true, - allowedValues: [ - { - self: 'https://siem-kibana.atlassian.net/rest/api/2/issuetype/10023', - id: '10023', - description: 'A problem or error.', - iconUrl: - 'https://siem-kibana.atlassian.net/secure/viewavatar?size=medium&avatarId=10303&avatarType=issuetype', - name: 'Bug', - subtask: false, - avatarId: 10303, - }, - ], - defaultValue: {}, - schema: { - type: 'issuetype', - }, - name: 'Issue Type', - }, - attachment: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'array', - items: 'attachment', - }, - name: 'Attachment', - }, - duedate: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'date', - }, - name: 'Due date', - }, - description: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'string', - }, - name: 'Description', - }, - project: { - required: true, - allowedValues: [ - { - self: 'https://siem-kibana.atlassian.net/rest/api/2/project/10015', - id: '10015', - key: 'RJ2', - name: 'RJ2', - projectTypeKey: 'business', - simplified: false, - avatarUrls: { - '48x48': - 'https://siem-kibana.atlassian.net/secure/projectavatar?pid=10015&avatarId=10412', - '24x24': - 'https://siem-kibana.atlassian.net/secure/projectavatar?size=small&s=small&pid=10015&avatarId=10412', - '16x16': - 'https://siem-kibana.atlassian.net/secure/projectavatar?size=xsmall&s=xsmall&pid=10015&avatarId=10412', - '32x32': - 'https://siem-kibana.atlassian.net/secure/projectavatar?size=medium&s=medium&pid=10015&avatarId=10412', - }, - }, - ], - defaultValue: {}, - schema: { - type: 'project', - }, - name: 'Project', - }, - assignee: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'user', - }, - name: 'Assignee', - }, - labels: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'array', - items: 'string', - }, - name: 'Labels', - }, -}; -const resilientFields: ResilientGetFieldsResponse = [ - { input_type: 'text', name: 'addr', read_only: false, text: 'Address' }, - { - input_type: 'boolean', - name: 'alberta_health_risk_assessment', - read_only: false, - text: 'Alberta Health Risk Assessment', - }, - { input_type: 'number', name: 'hard_liability', read_only: true, text: 'Assessed Liability' }, - { input_type: 'text', name: 'city', read_only: false, text: 'City' }, - { input_type: 'select', name: 'country', read_only: false, text: 'Country/Region' }, - { input_type: 'select_owner', name: 'creator_id', read_only: true, text: 'Created By' }, - { input_type: 'select', name: 'crimestatus_id', read_only: false, text: 'Criminal Activity' }, - { input_type: 'boolean', name: 'data_encrypted', read_only: false, text: 'Data Encrypted' }, - { input_type: 'select', name: 'data_format', read_only: false, text: 'Data Format' }, - { input_type: 'datetimepicker', name: 'end_date', read_only: true, text: 'Date Closed' }, - { input_type: 'datetimepicker', name: 'create_date', read_only: true, text: 'Date Created' }, - { - input_type: 'datetimepicker', - name: 'determined_date', - read_only: false, - text: 'Date Determined', - }, - { - input_type: 'datetimepicker', - name: 'discovered_date', - read_only: false, - required: 'always', - text: 'Date Discovered', - }, - { input_type: 'datetimepicker', name: 'start_date', read_only: false, text: 'Date Occurred' }, - { input_type: 'select', name: 'exposure_dept_id', read_only: false, text: 'Department' }, - { input_type: 'textarea', name: 'description', read_only: false, text: 'Description' }, - { input_type: 'boolean', name: 'employee_involved', read_only: false, text: 'Employee Involved' }, - { input_type: 'boolean', name: 'data_contained', read_only: false, text: 'Exposure Resolved' }, - { input_type: 'select', name: 'exposure_type_id', read_only: false, text: 'Exposure Type' }, - { - input_type: 'multiselect', - name: 'gdpr_breach_circumstances', - read_only: false, - text: 'GDPR Breach Circumstances', - }, - { input_type: 'select', name: 'gdpr_breach_type', read_only: false, text: 'GDPR Breach Type' }, - { - input_type: 'textarea', - name: 'gdpr_breach_type_comment', - read_only: false, - text: 'GDPR Breach Type Comment', - }, - { input_type: 'select', name: 'gdpr_consequences', read_only: false, text: 'GDPR Consequences' }, - { - input_type: 'textarea', - name: 'gdpr_consequences_comment', - read_only: false, - text: 'GDPR Consequences Comment', - }, - { - input_type: 'select', - name: 'gdpr_final_assessment', - read_only: false, - text: 'GDPR Final Assessment', - }, - { - input_type: 'textarea', - name: 'gdpr_final_assessment_comment', - read_only: false, - text: 'GDPR Final Assessment Comment', - }, - { - input_type: 'select', - name: 'gdpr_identification', - read_only: false, - text: 'GDPR Identification', - }, - { - input_type: 'textarea', - name: 'gdpr_identification_comment', - read_only: false, - text: 'GDPR Identification Comment', - }, - { - input_type: 'select', - name: 'gdpr_personal_data', - read_only: false, - text: 'GDPR Personal Data', - }, - { - input_type: 'textarea', - name: 'gdpr_personal_data_comment', - read_only: false, - text: 'GDPR Personal Data Comment', - }, - { - input_type: 'boolean', - name: 'gdpr_subsequent_notification', - read_only: false, - text: 'GDPR Subsequent Notification', - }, - { input_type: 'number', name: 'id', read_only: true, text: 'ID' }, - { input_type: 'boolean', name: 'impact_likely', read_only: false, text: 'Impact Likely' }, - { - input_type: 'boolean', - name: 'ny_impact_likely', - read_only: false, - text: 'Impact Likely for New York', - }, - { - input_type: 'boolean', - name: 'or_impact_likely', - read_only: false, - text: 'Impact Likely for Oregon', - }, - { - input_type: 'boolean', - name: 'wa_impact_likely', - read_only: false, - text: 'Impact Likely for Washington', - }, - { input_type: 'boolean', name: 'confirmed', read_only: false, text: 'Incident Disposition' }, - { input_type: 'multiselect', name: 'incident_type_ids', read_only: false, text: 'Incident Type' }, - { - input_type: 'text', - name: 'exposure_individual_name', - read_only: false, - text: 'Individual Name', - }, - { - input_type: 'select', - name: 'harmstatus_id', - read_only: false, - text: 'Is harm/risk/misuse foreseeable?', - }, - { input_type: 'text', name: 'jurisdiction_name', read_only: false, text: 'Jurisdiction' }, - { - input_type: 'datetimepicker', - name: 'inc_last_modified_date', - read_only: true, - text: 'Last Modified', - }, - { - input_type: 'multiselect', - name: 'gdpr_lawful_data_processing_categories', - read_only: false, - text: 'Lawful Data Processing Categories', - }, - { input_type: 'multiselect_members', name: 'members', read_only: false, text: 'Members' }, - { input_type: 'text', name: 'name', read_only: false, required: 'always', text: 'Name' }, - { input_type: 'boolean', name: 'negative_pr_likely', read_only: false, text: 'Negative PR' }, - { input_type: 'datetimepicker', name: 'due_date', read_only: true, text: 'Next Due Date' }, - { - input_type: 'multiselect', - name: 'nist_attack_vectors', - read_only: false, - text: 'NIST Attack Vectors', - }, - { input_type: 'select', name: 'org_handle', read_only: true, text: 'Organization' }, - { input_type: 'select_owner', name: 'owner_id', read_only: false, text: 'Owner' }, - { input_type: 'select', name: 'phase_id', read_only: true, text: 'Phase' }, - { - input_type: 'select', - name: 'pipeda_other_factors', - read_only: false, - text: 'PIPEDA Other Factors', - }, - { - input_type: 'textarea', - name: 'pipeda_other_factors_comment', - read_only: false, - text: 'PIPEDA Other Factors Comment', - }, - { - input_type: 'select', - name: 'pipeda_overall_assessment', - read_only: false, - text: 'PIPEDA Overall Assessment', - }, - { - input_type: 'textarea', - name: 'pipeda_overall_assessment_comment', - read_only: false, - text: 'PIPEDA Overall Assessment Comment', - }, - { - input_type: 'select', - name: 'pipeda_probability_of_misuse', - read_only: false, - text: 'PIPEDA Probability of Misuse', - }, - { - input_type: 'textarea', - name: 'pipeda_probability_of_misuse_comment', - read_only: false, - text: 'PIPEDA Probability of Misuse Comment', - }, - { - input_type: 'select', - name: 'pipeda_sensitivity_of_pi', - read_only: false, - text: 'PIPEDA Sensitivity of PI', - }, - { - input_type: 'textarea', - name: 'pipeda_sensitivity_of_pi_comment', - read_only: false, - text: 'PIPEDA Sensitivity of PI Comment', - }, - { input_type: 'text', name: 'reporter', read_only: false, text: 'Reporting Individual' }, - { - input_type: 'select', - name: 'resolution_id', - read_only: false, - required: 'close', - text: 'Resolution', - }, - { - input_type: 'textarea', - name: 'resolution_summary', - read_only: false, - required: 'close', - text: 'Resolution Summary', - }, - { input_type: 'select', name: 'gdpr_harm_risk', read_only: false, text: 'Risk of Harm' }, - { input_type: 'select', name: 'severity_code', read_only: false, text: 'Severity' }, - { input_type: 'boolean', name: 'inc_training', read_only: true, text: 'Simulation' }, - { input_type: 'multiselect', name: 'data_source_ids', read_only: false, text: 'Source of Data' }, - { input_type: 'select', name: 'state', read_only: false, text: 'State' }, - { input_type: 'select', name: 'plan_status', read_only: false, text: 'Status' }, - { input_type: 'select', name: 'exposure_vendor_id', read_only: false, text: 'Vendor' }, - { - input_type: 'boolean', - name: 'data_compromised', - read_only: false, - text: 'Was personal information or personal data involved?', - }, - { - input_type: 'select', - name: 'workspace', - read_only: false, - required: 'always', - text: 'Workspace', - }, - { input_type: 'text', name: 'zip', read_only: false, text: 'Zip' }, -]; -const serviceNowFields: ServiceNowGetFieldsResponse = [ - { - column_label: 'Approval', - mandatory: 'false', - max_length: '40', - element: 'approval', - }, - { - column_label: 'Close notes', - mandatory: 'false', - max_length: '4000', - element: 'close_notes', - }, - { - column_label: 'Contact type', - mandatory: 'false', - max_length: '40', - element: 'contact_type', - }, - { - column_label: 'Correlation display', - mandatory: 'false', - max_length: '100', - element: 'correlation_display', - }, - { - column_label: 'Correlation ID', - mandatory: 'false', - max_length: '100', - element: 'correlation_id', - }, - { - column_label: 'Description', - mandatory: 'false', - max_length: '4000', - element: 'description', - }, - { - column_label: 'Number', - mandatory: 'false', - max_length: '40', - element: 'number', - }, - { - column_label: 'Short description', - mandatory: 'false', - max_length: '160', - element: 'short_description', - }, - { - column_label: 'Created by', - mandatory: 'false', - max_length: '40', - element: 'sys_created_by', - }, - { - column_label: 'Updated by', - mandatory: 'false', - max_length: '40', - element: 'sys_updated_by', - }, - { - column_label: 'Upon approval', - mandatory: 'false', - max_length: '40', - element: 'upon_approval', - }, - { - column_label: 'Upon reject', - mandatory: 'false', - max_length: '40', - element: 'upon_reject', - }, -]; - -const formatFieldsTestData = [ - { - expected: [ - { id: 'summary', name: 'Summary', required: true, type: 'text' }, - { id: 'description', name: 'Description', required: false, type: 'text' }, - ], - fields: jiraFields, - type: ConnectorTypes.jira, - }, - { - expected: [ - { id: 'addr', name: 'Address', required: false, type: 'text' }, - { id: 'city', name: 'City', required: false, type: 'text' }, - { id: 'description', name: 'Description', required: false, type: 'textarea' }, - { - id: 'gdpr_breach_type_comment', - name: 'GDPR Breach Type Comment', - required: false, - type: 'textarea', - }, - { - id: 'gdpr_consequences_comment', - name: 'GDPR Consequences Comment', - required: false, - type: 'textarea', - }, - { - id: 'gdpr_final_assessment_comment', - name: 'GDPR Final Assessment Comment', - required: false, - type: 'textarea', - }, - { - id: 'gdpr_identification_comment', - name: 'GDPR Identification Comment', - required: false, - type: 'textarea', - }, - { - id: 'gdpr_personal_data_comment', - name: 'GDPR Personal Data Comment', - required: false, - type: 'textarea', - }, - { id: 'exposure_individual_name', name: 'Individual Name', required: false, type: 'text' }, - { id: 'jurisdiction_name', name: 'Jurisdiction', required: false, type: 'text' }, - { id: 'name', name: 'Name', required: true, type: 'text' }, - { - id: 'pipeda_other_factors_comment', - name: 'PIPEDA Other Factors Comment', - required: false, - type: 'textarea', - }, - { - id: 'pipeda_overall_assessment_comment', - name: 'PIPEDA Overall Assessment Comment', - required: false, - type: 'textarea', - }, - { - id: 'pipeda_probability_of_misuse_comment', - name: 'PIPEDA Probability of Misuse Comment', - required: false, - type: 'textarea', - }, - { - id: 'pipeda_sensitivity_of_pi_comment', - name: 'PIPEDA Sensitivity of PI Comment', - required: false, - type: 'textarea', - }, - { id: 'reporter', name: 'Reporting Individual', required: false, type: 'text' }, - { id: 'resolution_summary', name: 'Resolution Summary', required: false, type: 'textarea' }, - { id: 'zip', name: 'Zip', required: false, type: 'text' }, - ], - fields: resilientFields, - type: ConnectorTypes.resilient, - }, - { - expected: [ - { id: 'approval', name: 'Approval', required: false, type: 'text' }, - { id: 'close_notes', name: 'Close notes', required: false, type: 'textarea' }, - { id: 'contact_type', name: 'Contact type', required: false, type: 'text' }, - { id: 'correlation_display', name: 'Correlation display', required: false, type: 'text' }, - { id: 'correlation_id', name: 'Correlation ID', required: false, type: 'text' }, - { id: 'description', name: 'Description', required: false, type: 'textarea' }, - { id: 'number', name: 'Number', required: false, type: 'text' }, - { id: 'short_description', name: 'Short description', required: false, type: 'text' }, - { id: 'sys_created_by', name: 'Created by', required: false, type: 'text' }, - { id: 'sys_updated_by', name: 'Updated by', required: false, type: 'text' }, - { id: 'upon_approval', name: 'Upon approval', required: false, type: 'text' }, - { id: 'upon_reject', name: 'Upon reject', required: false, type: 'text' }, - ], - fields: serviceNowFields, - type: ConnectorTypes.servicenow, - }, -]; describe('client/configure/utils', () => { describe('formatFields', () => { formatFieldsTestData.forEach(({ expected, fields, type }) => { @@ -542,4 +22,23 @@ describe('client/configure/utils', () => { }); }); }); + describe('createDefaultMapping', () => { + formatFieldsTestData.forEach(({ expected, fields, type }) => { + it(`normalizes ${type} fields to common type ConnectorField`, () => { + const result = createDefaultMapping(expected, type); + expect(result).toEqual(mappings[type]); + }); + }); + it(`if the preferredField is not required and another field is, use the other field`, () => { + const result = createDefaultMapping( + [ + { id: 'summary', name: 'Summary', required: false, type: 'text' }, + { id: 'title', name: 'Title', required: true, type: 'text' }, + { id: 'description', name: 'Description', required: false, type: 'text' }, + ], + ConnectorTypes.jira + ); + expect(result).toEqual(mappings[`${ConnectorTypes.jira}-alt`]); + }); + }); }); diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts index 45ccb4f2c539fa..0d78bceeaf2fa9 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; +import { SavedObject } from 'kibana/server'; import { CaseStatuses, CommentAttributes, @@ -14,8 +14,8 @@ import { ESCaseAttributes, ESCasesConfigureAttributes, } from '../../../../common/api'; -import { mappings } from '../cases/configure/mock'; import { CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT } from '../../../saved_object_types'; +import { mappings } from '../../../client/configure/mock'; export const mockCases: Array> = [ { @@ -381,31 +381,13 @@ export const mockCaseConfigure: Array> = }, ]; -export const mockCaseConfigureFind: Array> = [ - { - page: 1, - per_page: 5, - total: mockCaseConfigure.length, - saved_objects: [{ ...mockCaseConfigure[0], score: 0 }], - }, -]; - export const mockCaseMappings: Array> = [ { type: CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT, id: 'mock-mappings-1', attributes: { - mappings, + mappings: mappings[ConnectorTypes.jira], }, references: [], }, ]; - -export const mockCaseMappingsFind: Array> = [ - { - page: 1, - per_page: 5, - total: mockCaseConfigure.length, - saved_objects: [{ ...mockCaseMappings[0], score: 0 }], - }, -]; diff --git a/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts b/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts index b6da21927e342e..efc3b6044a8045 100644 --- a/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts +++ b/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts @@ -3,9 +3,15 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { CasePostRequest, CasesConfigureRequest, ConnectorTypes } from '../../../../common/api'; +import { + CasePostRequest, + CasesConfigureRequest, + ConnectorTypes, + PostPushRequest, +} from '../../../../common/api'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { FindActionResult } from '../../../../../actions/server/types'; +import { params } from '../cases/configure/mock'; export const newCase: CasePostRequest = { title: 'My new case', @@ -76,3 +82,19 @@ export const newConfiguration: CasesConfigureRequest = { }, closure_type: 'close-by-pushing', }; + +export const newPostPushRequest: PostPushRequest = { + params: params[ConnectorTypes.jira], + connector_type: ConnectorTypes.jira, +}; + +export const executePushResponse = { + status: 'ok', + data: { + title: 'RJ2-200', + id: '10663', + pushedDate: '2020-12-17T00:32:40.738Z', + url: 'https://siem-kibana.atlassian.net/browse/RJ2-200', + comments: [], + }, +}; diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts index d75f42f6e486bc..87e165f8e00147 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts @@ -17,7 +17,8 @@ import { import { initGetCaseConfigure } from './get_configure'; import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; -import { mappings } from './mock'; +import { mappings } from '../../../../client/configure/mock'; +import { ConnectorTypes } from '../../../../../common/api/connectors'; describe('GET configuration', () => { let routeHandler: RequestHandler; @@ -42,7 +43,7 @@ describe('GET configuration', () => { expect(res.status).toEqual(200); expect(res.payload).toEqual({ ...mockCaseConfigure[0].attributes, - mappings, + mappings: mappings[ConnectorTypes.jira], version: mockCaseConfigure[0].version, }); }); @@ -76,7 +77,7 @@ describe('GET configuration', () => { email: 'testemail@elastic.co', username: 'elastic', }, - mappings, + mappings: mappings[ConnectorTypes.jira], updated_at: '2020-04-09T09:43:51.778Z', updated_by: { full_name: 'elastic', diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_fields.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_fields.ts deleted file mode 100644 index c9b8e671b7df82..00000000000000 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_fields.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from '@hapi/boom'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; -import { RouteDeps } from '../../types'; -import { escapeHatch, wrapError } from '../../utils'; - -import { CASE_CONFIGURE_CONNECTOR_DETAILS_URL } from '../../../../../common/constants'; -import { - ConnectorRequestParamsRt, - GetFieldsRequestQueryRt, - throwErrors, -} from '../../../../../common/api'; - -export function initCaseConfigureGetFields({ router }: RouteDeps) { - router.get( - { - path: CASE_CONFIGURE_CONNECTOR_DETAILS_URL, - validate: { - params: escapeHatch, - query: escapeHatch, - }, - }, - async (context, request, response) => { - try { - if (!context.case) { - throw Boom.badRequest('RouteHandlerContext is not registered for cases'); - } - const query = pipe( - GetFieldsRequestQueryRt.decode(request.query), - fold(throwErrors(Boom.badRequest), identity) - ); - const params = pipe( - ConnectorRequestParamsRt.decode(request.params), - fold(throwErrors(Boom.badRequest), identity) - ); - - const caseClient = context.case.getCaseClient(); - - const connectorType = query.connector_type; - if (connectorType == null) { - throw Boom.illegal('no connectorType value provided'); - } - - const actionsClient = await context.actions?.getActionsClient(); - if (actionsClient == null) { - throw Boom.notFound('Action client have not been found'); - } - - const res = await caseClient.getFields({ - actionsClient, - connectorId: params.connector_id, - connectorType, - }); - - return response.ok({ - body: res.fields, - }); - } catch (error) { - return response.customError(wrapError(error)); - } - } - ); -} diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/mock.ts b/x-pack/plugins/case/server/routes/api/cases/configure/mock.ts index ed8b2088646115..771b09cec2a359 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/mock.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/mock.ts @@ -7,6 +7,7 @@ import { ServiceConnectorCaseParams, ServiceConnectorCommentParams, ConnectorMappingsAttributes, + ConnectorTypes, } from '../../../../../common/api/connectors'; export const updateUser = { updatedAt: '2020-03-13T08:34:53.450Z', @@ -24,16 +25,36 @@ export const comment: ServiceConnectorCommentParams = { ...entity, }; export const defaultPipes = ['informationCreated']; -export const params = { +const basicParams = { comments: [comment], description: 'a description', - impact: '3', - savedObjectId: '1231231231232', - severity: '1', title: 'a title', - urgency: '2', - ...entity, -} as ServiceConnectorCaseParams; + savedObjectId: '1231231231232', + externalId: null, +}; +export const params = { + [ConnectorTypes.jira]: { + ...basicParams, + issueType: '10003', + priority: 'Highest', + parent: '5002', + ...entity, + } as ServiceConnectorCaseParams, + [ConnectorTypes.resilient]: { + ...basicParams, + incidentTypes: ['10003'], + severityCode: '1', + ...entity, + } as ServiceConnectorCaseParams, + [ConnectorTypes.servicenow]: { + ...basicParams, + impact: '3', + severity: '1', + urgency: '2', + ...entity, + } as ServiceConnectorCaseParams, + [ConnectorTypes.none]: {}, +}; export const mappings: ConnectorMappingsAttributes[] = [ { source: 'title', diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.test.ts new file mode 100644 index 00000000000000..ff0939fdcce1fa --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.test.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { kibanaResponseFactory, RequestHandler, RequestHandlerContext } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCaseMappings, +} from '../../__fixtures__'; + +import { initPostPushToService } from './post_push_to_service'; +import { executePushResponse, newPostPushRequest } from '../../__mocks__/request_responses'; +import { CASE_CONFIGURE_PUSH_URL } from '../../../../../common/constants'; + +describe('Post push to service', () => { + let routeHandler: RequestHandler; + const req = httpServerMock.createKibanaRequest({ + path: `${CASE_CONFIGURE_PUSH_URL}`, + method: 'post', + params: { + connector_id: '666', + }, + body: newPostPushRequest, + }); + let context: RequestHandlerContext; + beforeAll(async () => { + routeHandler = await createRoute(initPostPushToService, 'post'); + const spyOnDate = jest.spyOn(global, 'Date') as jest.SpyInstance<{}, []>; + spyOnDate.mockImplementation(() => ({ + toISOString: jest.fn().mockReturnValue('2020-04-09T09:43:51.778Z'), + })); + context = await createRouteContext( + createMockSavedObjectsRepository({ + caseMappingsSavedObject: mockCaseMappings, + }) + ); + }); + + it('Happy path - posts success', async () => { + const betterContext = ({ + ...context, + actions: { + ...context.actions, + getActionsClient: () => { + const actions = context!.actions!.getActionsClient(); + return { + ...actions, + execute: jest.fn().mockImplementation(({ actionId }) => { + return { + status: 'ok', + data: { + title: 'RJ2-200', + id: '10663', + pushedDate: '2020-12-17T00:32:40.738Z', + url: 'https://siem-kibana.atlassian.net/browse/RJ2-200', + comments: [], + }, + actionId, + }; + }), + }; + }, + }, + } as unknown) as RequestHandlerContext; + + const res = await routeHandler(betterContext, req, kibanaResponseFactory); + + expect(res.status).toEqual(200); + expect(res.payload).toEqual({ + ...executePushResponse, + actionId: '666', + }); + }); + it('Unhappy path - context case missing', async () => { + const betterContext = ({ + ...context, + case: null, + } as unknown) as RequestHandlerContext; + + const res = await routeHandler(betterContext, req, kibanaResponseFactory); + expect(res.status).toEqual(400); + expect(res.payload.isBoom).toBeTruthy(); + expect(res.payload.output.payload.message).toEqual( + 'RouteHandlerContext is not registered for cases' + ); + }); + it('Unhappy path - context actions missing', async () => { + const betterContext = ({ + ...context, + actions: null, + } as unknown) as RequestHandlerContext; + + const res = await routeHandler(betterContext, req, kibanaResponseFactory); + expect(res.status).toEqual(404); + expect(res.payload.isBoom).toBeTruthy(); + expect(res.payload.output.payload.message).toEqual('Action client have not been found'); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.ts b/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.ts index 9c4c06c5f4e186..fb7a91d0463136 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.ts @@ -19,7 +19,7 @@ import { } from '../../../../../common/api'; import { mapIncident } from './utils'; -export function initPostPushToService({ router, connectorMappingsService }: RouteDeps) { +export function initPostPushToService({ router }: RouteDeps) { router.post( { path: CASE_CONFIGURE_PUSH_URL, diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/utils.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/utils.test.ts index d2ecdf61c882d7..d1f8391ad082a0 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/utils.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/utils.test.ts @@ -5,25 +5,31 @@ */ import { + mapIncident, prepareFieldsForTransformation, - transformFields, + serviceFormatter, transformComments, transformers, + transformFields, } from './utils'; -import { comment as commentObj, defaultPipes, mappings, params, updateUser } from './mock'; +import { comment as commentObj, mappings, defaultPipes, params, updateUser } from './mock'; import { - ServiceConnectorCaseParams, + ConnectorTypes, ExternalServiceParams, Incident, + ServiceConnectorCaseParams, } from '../../../../../common/api/connectors'; +import { actionsClientMock } from '../../../../../../actions/server/actions_client.mock'; +import { mappings as mappingsMock } from '../../../../client/configure/mock'; const formatComment = { commentId: commentObj.commentId, comment: commentObj.comment }; +const serviceNowParams = params[ConnectorTypes.servicenow] as ServiceConnectorCaseParams; describe('api/cases/configure/utils', () => { describe('prepareFieldsForTransformation', () => { test('prepare fields with defaults', () => { const res = prepareFieldsForTransformation({ defaultPipes, - params, + params: serviceNowParams, mappings, }); expect(res).toEqual([ @@ -46,7 +52,7 @@ describe('api/cases/configure/utils', () => { const res = prepareFieldsForTransformation({ defaultPipes: ['myTestPipe'], mappings, - params, + params: serviceNowParams, }); expect(res).toEqual([ { @@ -69,11 +75,11 @@ describe('api/cases/configure/utils', () => { const fields = prepareFieldsForTransformation({ defaultPipes, mappings, - params, + params: serviceNowParams, }); const res = transformFields({ - params, + params: serviceNowParams, fields, }); @@ -85,14 +91,14 @@ describe('api/cases/configure/utils', () => { test('transform fields for update correctly', () => { const fields = prepareFieldsForTransformation({ - params, + params: serviceNowParams, mappings, defaultPipes: ['informationUpdated'], }); const res = transformFields({ params: { - ...params, + ...serviceNowParams, updatedAt: '2020-03-15T08:34:53.450Z', updatedBy: { username: 'anotherUser', @@ -114,13 +120,13 @@ describe('api/cases/configure/utils', () => { test('add newline character to description', () => { const fields = prepareFieldsForTransformation({ - params, + params: serviceNowParams, mappings, defaultPipes: ['informationUpdated'], }); const res = transformFields({ - params, + params: serviceNowParams, fields, currentIncident: { short_description: 'first title', @@ -134,12 +140,12 @@ describe('api/cases/configure/utils', () => { const fields = prepareFieldsForTransformation({ defaultPipes, mappings, - params, + params: serviceNowParams, }); const res = transformFields({ params: { - ...params, + ...serviceNowParams, createdBy: { fullName: '', username: 'elastic' }, }, fields, @@ -155,12 +161,12 @@ describe('api/cases/configure/utils', () => { const fields = prepareFieldsForTransformation({ defaultPipes: ['informationUpdated'], mappings, - params, + params: serviceNowParams, }); const res = transformFields({ params: { - ...params, + ...serviceNowParams, updatedAt: '2020-03-15T08:34:53.450Z', updatedBy: { username: 'anotherUser', fullName: '' }, }, @@ -382,4 +388,142 @@ describe('api/cases/configure/utils', () => { }); }); }); + describe('mapIncident', () => { + let actionsMock = actionsClientMock.create(); + it('maps an external incident', async () => { + const res = await mapIncident( + actionsMock, + '123', + ConnectorTypes.servicenow, + mappingsMock[ConnectorTypes.servicenow], + serviceNowParams + ); + expect(res).toEqual({ + incident: { + description: 'a description (created at 2020-03-13T08:34:53.450Z by Elastic User)', + externalId: null, + impact: '3', + severity: '1', + short_description: 'a title (created at 2020-03-13T08:34:53.450Z by Elastic User)', + urgency: '2', + }, + comments: [ + { + comment: 'first comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', + commentId: 'b5b4c4d0-574e-11ea-9e2e-21b90f8a9631', + }, + ], + }); + }); + it('throws error if invalid service', async () => { + await mapIncident( + actionsMock, + '123', + 'invalid', + mappingsMock[ConnectorTypes.servicenow], + serviceNowParams + ).catch((e) => { + expect(e).not.toBeNull(); + expect(e).toEqual(new Error(`Invalid service`)); + }); + }); + it('updates an existing incident', async () => { + const existingIncidentData = { + description: 'fun description', + impact: '3', + severity: '3', + short_description: 'fun title', + urgency: '3', + }; + const execute = jest.fn().mockReturnValue(existingIncidentData); + actionsMock = { ...actionsMock, execute }; + const res = await mapIncident( + actionsMock, + '123', + ConnectorTypes.servicenow, + mappingsMock[ConnectorTypes.servicenow], + { ...serviceNowParams, externalId: '123' } + ); + expect(res).toEqual({ + incident: { + description: 'a description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + externalId: '123', + impact: '3', + severity: '1', + short_description: 'a title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + urgency: '2', + }, + comments: [ + { + comment: 'first comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', + commentId: 'b5b4c4d0-574e-11ea-9e2e-21b90f8a9631', + }, + ], + }); + }); + it('throws error when existing incident throws', async () => { + const execute = jest.fn().mockImplementation(() => { + throw new Error('exception'); + }); + actionsMock = { ...actionsMock, execute }; + await mapIncident( + actionsMock, + '123', + ConnectorTypes.servicenow, + mappingsMock[ConnectorTypes.servicenow], + { ...serviceNowParams, externalId: '123' } + ).catch((e) => { + expect(e).not.toBeNull(); + expect(e).toEqual( + new Error( + `Retrieving Incident by id 123 from ServiceNow failed with exception: Error: exception` + ) + ); + }); + }); + }); + + const connectors = [ + { + name: ConnectorTypes.jira, + result: { + incident: { + issueType: '10003', + parent: '5002', + priority: 'Highest', + }, + thirdPartyName: 'Jira', + }, + }, + { + name: ConnectorTypes.resilient, + result: { + incident: { + incidentTypes: ['10003'], + severityCode: '1', + }, + thirdPartyName: 'Resilient', + }, + }, + { + name: ConnectorTypes.servicenow, + result: { + incident: { + impact: '3', + severity: '1', + urgency: '2', + }, + thirdPartyName: 'ServiceNow', + }, + }, + ]; + describe('serviceFormatter', () => { + connectors.forEach((c) => + it(`formats ${c.name}`, () => { + const caseParams = params[c.name] as ServiceConnectorCaseParams; + const res = serviceFormatter(c.name, caseParams); + expect(res).toEqual(c.result); + }) + ); + }); }); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts b/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts index b8a37661fe9f77..89109af4cecb9d 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts @@ -25,9 +25,7 @@ import { TransformerArgs, TransformFieldsArgs, } from '../../../../../common/api'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ActionsClient } from '../../../../../../actions/server/actions_client'; - +import { ActionsClient } from '../../../../../../actions/server'; export const mapIncident = async ( actionsClient: ActionsClient, connectorId: string, @@ -59,13 +57,11 @@ export const mapIncident = async ( ); } } - const fields = prepareFieldsForTransformation({ defaultPipes, mappings, params, }); - const transformedFields = transformFields< ServiceConnectorCaseParams, ExternalServiceParams, diff --git a/x-pack/plugins/case/server/routes/api/index.ts b/x-pack/plugins/case/server/routes/api/index.ts index 587e43b218f446..15817b425021e9 100644 --- a/x-pack/plugins/case/server/routes/api/index.ts +++ b/x-pack/plugins/case/server/routes/api/index.ts @@ -25,7 +25,6 @@ import { initPostCommentApi } from './cases/comments/post_comment'; import { initCaseConfigureGetActionConnector } from './cases/configure/get_connectors'; import { initGetCaseConfigure } from './cases/configure/get_configure'; -import { initCaseConfigureGetFields } from './cases/configure/get_fields'; import { initPatchCaseConfigure } from './cases/configure/patch_configure'; import { initPostCaseConfigure } from './cases/configure/post_configure'; import { initPostPushToService } from './cases/configure/post_push_to_service'; @@ -54,7 +53,6 @@ export function initCaseApi(deps: RouteDeps) { initGetCaseConfigure(deps); initPatchCaseConfigure(deps); initPostCaseConfigure(deps); - initCaseConfigureGetFields(deps); initPostPushToService(deps); // Reporters initGetReportersApi(deps); diff --git a/x-pack/plugins/case/server/routes/api/utils.ts b/x-pack/plugins/case/server/routes/api/utils.ts index c8753772648c29..917afb487b1f44 100644 --- a/x-pack/plugins/case/server/routes/api/utils.ts +++ b/x-pack/plugins/case/server/routes/api/utils.ts @@ -96,7 +96,7 @@ export function wrapError(error: any): CustomHttpResponseOptions const boom = isBoom(error) ? error : boomify(error, options); return { body: boom, - headers: boom.output.headers, + headers: boom.output.headers as { [key: string]: string }, statusCode: boom.output.statusCode, }; } diff --git a/x-pack/plugins/dashboard_enhanced/kibana.json b/x-pack/plugins/dashboard_enhanced/kibana.json index b24c0b6983f407..d3a1f29f0c7ffd 100644 --- a/x-pack/plugins/dashboard_enhanced/kibana.json +++ b/x-pack/plugins/dashboard_enhanced/kibana.json @@ -9,7 +9,6 @@ "embeddable", "kibanaUtils", "embeddableEnhanced", - "kibanaReact", - "uiActions" + "kibanaReact" ] } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/types.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/types.ts index 7f5137812ee324..d2d3c37a692874 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/types.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/types.ts @@ -5,7 +5,7 @@ */ import { UiActionsEnhancedBaseActionFactoryContext } from '../../../../../ui_actions_enhanced/public'; -import { APPLY_FILTER_TRIGGER } from '../../../../../../../src/plugins/ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../../../../../../../src/plugins/data/public'; import { DrilldownConfig } from '../../../../common'; export type Config = DrilldownConfig; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts index e1d8a2b3671a25..ff79cda1bb2158 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { APPLY_FILTER_TRIGGER } from '../../../../../../../src/plugins/data/public'; import { - APPLY_FILTER_TRIGGER, SELECT_RANGE_TRIGGER, - TriggerId, VALUE_CLICK_TRIGGER, -} from '../../../../../../../src/plugins/ui_actions/public'; +} from '../../../../../../../src/plugins/embeddable/public'; +import { TriggerId } from '../../../../../../../src/plugins/ui_actions/public'; /** * We know that VALUE_CLICK_TRIGGER and SELECT_RANGE_TRIGGER are also triggering APPLY_FILTER_TRIGGER. diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx index 921c2aed00624a..c2bf48188c313d 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx @@ -4,17 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - TriggerContextMapping, - APPLY_FILTER_TRIGGER, -} from '../../../../../../../src/plugins/ui_actions/public'; +import { TriggerContextMapping } from '../../../../../../../src/plugins/ui_actions/public'; import { DashboardUrlGeneratorState } from '../../../../../../../src/plugins/dashboard/public'; import { + APPLY_FILTER_TRIGGER, esFilters, + Filter, isFilters, isQuery, isTimeRange, + Query, + TimeRange, } from '../../../../../../../src/plugins/data/public'; +import { IEmbeddable, EmbeddableInput } from '../../../../../../../src/plugins/embeddable/public'; import { AbstractDashboardDrilldown, AbstractDashboardDrilldownParams, @@ -24,6 +26,12 @@ import { KibanaURL } from '../../../../../../../src/plugins/share/public'; import { EMBEDDABLE_TO_DASHBOARD_DRILLDOWN } from './constants'; import { createExtract, createInject } from '../../../../common'; +interface EmbeddableQueryInput extends EmbeddableInput { + query?: Query; + filters?: Filter[]; + timeRange?: TimeRange; +} + type Trigger = typeof APPLY_FILTER_TRIGGER; type Context = TriggerContextMapping[Trigger]; export type Params = AbstractDashboardDrilldownParams; @@ -46,7 +54,8 @@ export class EmbeddableToDashboardDrilldown extends AbstractDashboardDrilldown; + const input = embeddable.getInput(); if (isQuery(input.query) && config.useCurrentFilters) state.query = input.query; // if useCurrentDashboardDataRange is enabled, then preserve current time range diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.ts index 52946b3dca7a1f..2f983ba2476254 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.ts @@ -13,11 +13,14 @@ import { ApplyGlobalFilterActionContext, esFilters, } from '../../../../../../src/plugins/data/public'; +import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; import { KibanaURL } from '../../../../../../src/plugins/share/public'; import * as shared from './shared'; import { AbstractExploreDataAction } from './abstract_explore_data_action'; -export type ExploreDataChartActionContext = ApplyGlobalFilterActionContext; +export interface ExploreDataChartActionContext extends ApplyGlobalFilterActionContext { + embeddable?: IEmbeddable; +} export const ACTION_EXPLORE_DATA_CHART = 'ACTION_EXPLORE_DATA_CHART'; diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts index fdd096e718339d..fda07fb47ab0d7 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts @@ -5,12 +5,25 @@ */ import { Action } from '../../../../../../src/plugins/ui_actions/public'; -import { EmbeddableContext } from '../../../../../../src/plugins/embeddable/public'; +import { + EmbeddableContext, + EmbeddableInput, + IEmbeddable, +} from '../../../../../../src/plugins/embeddable/public'; +import { Query, Filter, TimeRange } from '../../../../../../src/plugins/data/public'; import { DiscoverUrlGeneratorState } from '../../../../../../src/plugins/discover/public'; import { KibanaURL } from '../../../../../../src/plugins/share/public'; import * as shared from './shared'; import { AbstractExploreDataAction } from './abstract_explore_data_action'; +interface EmbeddableQueryInput extends EmbeddableInput { + query?: Query; + filters?: Filter[]; + timeRange?: TimeRange; +} + +type EmbeddableQueryContext = EmbeddableContext>; + export const ACTION_EXPLORE_DATA = 'ACTION_EXPLORE_DATA'; /** @@ -18,15 +31,15 @@ export const ACTION_EXPLORE_DATA = 'ACTION_EXPLORE_DATA'; * menu of a dashboard panel. */ export class ExploreDataContextMenuAction - extends AbstractExploreDataAction - implements Action { + extends AbstractExploreDataAction + implements Action { public readonly id = ACTION_EXPLORE_DATA; public readonly type = ACTION_EXPLORE_DATA; public readonly order = 200; - protected readonly getUrl = async (context: EmbeddableContext): Promise => { + protected readonly getUrl = async (context: EmbeddableQueryContext): Promise => { const { plugins } = this.params.start(); const { urlGenerator } = plugins.discover; diff --git a/x-pack/plugins/discover_enhanced/public/plugin.ts b/x-pack/plugins/discover_enhanced/public/plugin.ts index f1273ab00bdd40..78f3464484ccf3 100644 --- a/x-pack/plugins/discover_enhanced/public/plugin.ts +++ b/x-pack/plugins/discover_enhanced/public/plugin.ts @@ -6,11 +6,8 @@ import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { PluginInitializerContext } from 'kibana/public'; -import { - UiActionsSetup, - UiActionsStart, - APPLY_FILTER_TRIGGER, -} from '../../../../src/plugins/ui_actions/public'; +import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../../../../src/plugins/data/public'; import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public'; import { DiscoverSetup, DiscoverStart } from '../../../../src/plugins/discover/public'; import { SharePluginSetup, SharePluginStart } from '../../../../src/plugins/share/public'; diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts index e0627c521bb795..23e73bea5dfec6 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts @@ -5,6 +5,7 @@ */ import { DatatableColumnType } from '../../../../../../../src/plugins/expressions/common'; +import { Query, Filter, TimeRange } from '../../../../../../../src/plugins/data/public'; import { Embeddable, EmbeddableInput, @@ -159,6 +160,9 @@ export const rowClickData = { interface TestInput extends EmbeddableInput { savedObjectId?: string; + query?: Query; + filters?: Filter[]; + timeRange?: TimeRange; } interface TestOutput extends EmbeddableOutput { diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts index 24406cefce7a21..e3730084d70207 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts @@ -6,14 +6,11 @@ import { IExternalUrl } from 'src/core/public'; import { UrlDrilldown, ActionContext, Config } from './url_drilldown'; -import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public/lib/embeddables'; +import { IEmbeddable, VALUE_CLICK_TRIGGER } from '../../../../../../src/plugins/embeddable/public'; import { DatatableColumnType } from '../../../../../../src/plugins/expressions/common'; import { of } from '../../../../../../src/plugins/kibana_utils'; import { createPoint, rowClickData, TestEmbeddable } from './test/data'; -import { - VALUE_CLICK_TRIGGER, - ROW_CLICK_TRIGGER, -} from '../../../../../../src/plugins/ui_actions/public'; +import { ROW_CLICK_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; const mockDataPoints = [ { diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx index 8dfb2c54c5ab02..bfeab263d20e31 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx @@ -12,13 +12,13 @@ import { ChartActionContext, CONTEXT_MENU_TRIGGER, IEmbeddable, -} from '../../../../../../src/plugins/embeddable/public'; -import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../../src/plugins/kibana_utils/public'; -import { - ROW_CLICK_TRIGGER, + EmbeddableInput, SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, -} from '../../../../../../src/plugins/ui_actions/public'; +} from '../../../../../../src/plugins/embeddable/public'; +import { ROW_CLICK_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; +import { Query, Filter, TimeRange } from '../../../../../../src/plugins/data/public'; +import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../../src/plugins/kibana_utils/public'; import { UiActionsEnhancedDrilldownDefinition as Drilldown, UrlDrilldownGlobalScope, @@ -31,6 +31,15 @@ import { import { getPanelVariables, getEventScope, getEventVariableList } from './url_drilldown_scope'; import { txtUrlDrilldownDisplayName } from './i18n'; +interface EmbeddableQueryInput extends EmbeddableInput { + query?: Query; + filters?: Filter[]; + timeRange?: TimeRange; +} + +/** @internal */ +export type EmbeddableWithQueryInput = IEmbeddable; + interface UrlDrilldownDeps { externalUrl: IExternalUrl; getGlobalScope: () => UrlDrilldownGlobalScope; @@ -39,7 +48,7 @@ interface UrlDrilldownDeps { getVariablesHelpDocsLink: () => string; } -export type ActionContext = ChartActionContext; +export type ActionContext = ChartActionContext; export type Config = UrlDrilldownConfig; export type UrlTrigger = | typeof VALUE_CLICK_TRIGGER @@ -48,7 +57,7 @@ export type UrlTrigger = | typeof CONTEXT_MENU_TRIGGER; export interface ActionFactoryContext extends BaseActionFactoryContext { - embeddable?: IEmbeddable; + embeddable?: EmbeddableWithQueryInput; } export type CollectConfigProps = CollectConfigPropsBase; diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts index 3e5fc0a968d39c..12b74d475e9322 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts @@ -11,20 +11,24 @@ import type { Filter, Query, TimeRange } from '../../../../../../src/plugins/data/public'; import { - IEmbeddable, isRangeSelectTriggerContext, isValueClickTriggerContext, isRowClickTriggerContext, isContextMenuTriggerContext, RangeSelectContext, + SELECT_RANGE_TRIGGER, ValueClickContext, + VALUE_CLICK_TRIGGER, + EmbeddableInput, EmbeddableOutput, } from '../../../../../../src/plugins/embeddable/public'; -import type { ActionContext, ActionFactoryContext } from './url_drilldown'; +import type { + ActionContext, + ActionFactoryContext, + EmbeddableWithQueryInput, +} from './url_drilldown'; import { - SELECT_RANGE_TRIGGER, RowClickContext, - VALUE_CLICK_TRIGGER, ROW_CLICK_TRIGGER, } from '../../../../../../src/plugins/ui_actions/public'; @@ -32,7 +36,7 @@ import { * Part of context scope extracted from an embeddable * Expose on the scope as: `{{context.panel.id}}`, `{{context.panel.filters.[0]}}` */ -interface EmbeddableUrlDrilldownContextScope { +interface EmbeddableUrlDrilldownContextScope extends EmbeddableInput { /** * ID of the embeddable panel. */ @@ -59,10 +63,8 @@ interface EmbeddableUrlDrilldownContextScope { savedObjectId?: string; } -export function getPanelVariables(contextScopeInput: { - embeddable?: IEmbeddable; -}): EmbeddableUrlDrilldownContextScope { - function hasEmbeddable(val: unknown): val is { embeddable: IEmbeddable } { +export function getPanelVariables(contextScopeInput: unknown): EmbeddableUrlDrilldownContextScope { + function hasEmbeddable(val: unknown): val is { embeddable: EmbeddableWithQueryInput } { if (val && typeof val === 'object' && 'embeddable' in val) return true; return false; } @@ -99,7 +101,7 @@ function getIndexPatternIds(output: EmbeddableOutput): string[] { } export function getEmbeddableVariables( - embeddable: IEmbeddable + embeddable: EmbeddableWithQueryInput ): EmbeddableUrlDrilldownContextScope { const input = embeddable.getInput(); const output = embeddable.getOutput(); @@ -195,10 +197,10 @@ function getEventScopeFromValueClickTriggerContext( }); } -function getEventScopeFromRowClickTriggerContext({ - embeddable, - data, -}: RowClickContext): RowClickTriggerEventScope { +function getEventScopeFromRowClickTriggerContext(ctx: RowClickContext): RowClickTriggerEventScope { + const { data } = ctx; + const embeddable = ctx.embeddable as EmbeddableWithQueryInput; + const { rowIndex } = data; const columns = data.columns || data.table.columns.map(({ id }) => id); const values: Primitive[] = []; diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts index 9856d3a558e24a..8361b002c8206b 100644 --- a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts @@ -12,7 +12,7 @@ import { import { UiActionsEnhancedSerializedEvent } from '../../../ui_actions_enhanced/public'; import { of } from '../../../../../src/plugins/kibana_utils/public'; // use real const to make test fail in case someone accidentally changes it -import { APPLY_FILTER_TRIGGER } from '../../../../../src/plugins/ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../../../../../src/plugins/data/public'; class TestEmbeddable extends Embeddable { public readonly type = 'test'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx index 5406a90a75a35d..27c3410767d8ad 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx @@ -4,4 +4,50 @@ * you may not use this file except in compliance with the Elastic License. */ -// TODO: This will be used shortly in an upcoming PR +import { i18n } from '@kbn/i18n'; + +export const FLYOUT_ARIA_LABEL_ID = 'documentCreationFlyoutHeadingId'; + +export const FLYOUT_CANCEL_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.flyoutCancel', + { defaultMessage: 'Cancel' } +); +export const FLYOUT_CONTINUE_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.flyoutContinue', + { defaultMessage: 'Continue' } +); + +// This is indented the way it is to work with ApiCodeExample. +// Use dedent() when calling this alone +export const DOCUMENTS_API_JSON_EXAMPLE = `[ + { + "id": "park_rocky-mountain", + "title": "Rocky Mountain", + "description": "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra. Wildlife including mule deer, bighorn sheep, black bears, and cougars inhabit its igneous mountains and glacial valleys. Longs Peak, a classic Colorado fourteener, and the scenic Bear Lake are popular destinations, as well as the historic Trail Ridge Road, which reaches an elevation of more than 12,000 feet (3,700 m).", + "nps_link": "https://www.nps.gov/romo/index.htm", + "states": [ + "Colorado" + ], + "visitors": 4517585, + "world_heritage_site": false, + "location": "40.4,-105.58", + "acres": 265795.2, + "square_km": 1075.6, + "date_established": "1915-01-26T06:00:00Z" + }, + { + "id": "park_saguaro", + "title": "Saguaro", + "description": "Split into the separate Rincon Mountain and Tucson Mountain districts, this park is evidence that the dry Sonoran Desert is still home to a great variety of life spanning six biotic communities. Beyond the namesake giant saguaro cacti, there are barrel cacti, chollas, and prickly pears, as well as lesser long-nosed bats, spotted owls, and javelinas.", + "nps_link": "https://www.nps.gov/sagu/index.htm", + "states": [ + "Arizona" + ], + "visitors": 820426, + "world_heritage_site": false, + "location": "32.25,-110.5", + "acres": 91715.72, + "square_km": 371.2, + "date_established": "1994-10-14T05:00:00Z" + } + ]`; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.test.tsx new file mode 100644 index 00000000000000..ddce27789b82cb --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.test.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../../__mocks__/enterprise_search_url.mock'; +import { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock'; + +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { EuiCode, EuiCodeBlock, EuiButtonEmpty } from '@elastic/eui'; + +import { ApiCodeExample, FlyoutHeader, FlyoutBody, FlyoutFooter } from './api_code_example'; + +describe('ApiCodeExample', () => { + const values = { + engineName: 'test-engine', + engine: { apiKey: 'test-key' }, + }; + const actions = { + closeDocumentCreation: jest.fn(), + }; + + beforeAll(() => { + setMockValues(values); + setMockActions(actions); + }); + + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find(FlyoutHeader)).toHaveLength(1); + expect(wrapper.find(FlyoutBody)).toHaveLength(1); + expect(wrapper.find(FlyoutFooter)).toHaveLength(1); + }); + + describe('FlyoutHeader', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find('h2').text()).toEqual('Indexing by API'); + }); + }); + + describe('FlyoutBody', () => { + let wrapper: ShallowWrapper; + + beforeAll(() => { + wrapper = shallow(); + }); + + it('renders with the full remote Enterprise Search API URL', () => { + expect(wrapper.find(EuiCode).dive().dive().text()).toEqual( + 'http://localhost:3002/api/as/v1/engines/test-engine/documents' + ); + expect(wrapper.find(EuiCodeBlock).dive().dive().text()).toEqual( + expect.stringContaining('http://localhost:3002/api/as/v1/engines/test-engine/documents') + ); + }); + + it('renders with the API key', () => { + expect(wrapper.find(EuiCodeBlock).dive().dive().text()).toEqual( + expect.stringContaining('test-key') + ); + }); + }); + + describe('FlyoutFooter', () => { + it('closes the flyout', () => { + const wrapper = shallow(); + + wrapper.find(EuiButtonEmpty).simulate('click'); + expect(actions.closeDocumentCreation).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx new file mode 100644 index 00000000000000..9ebe404659ca2c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import dedent from 'dedent'; +import React from 'react'; +import { useValues, useActions } from 'kea'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiButtonEmpty, + EuiText, + EuiLink, + EuiSpacer, + EuiPanel, + EuiBadge, + EuiCode, + EuiCodeBlock, +} from '@elastic/eui'; + +import { getEnterpriseSearchUrl } from '../../../../shared/enterprise_search_url'; +import { EngineLogic } from '../../engine'; +import { EngineDetails } from '../../engine/types'; + +import { DOCS_PREFIX } from '../../../routes'; +import { + DOCUMENTS_API_JSON_EXAMPLE, + FLYOUT_ARIA_LABEL_ID, + FLYOUT_CANCEL_BUTTON, +} from '../constants'; +import { DocumentCreationLogic } from '../'; + +export const ApiCodeExample: React.FC = () => ( + <> + + + + +); + +export const FlyoutHeader: React.FC = () => { + return ( + + +

+ {i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.api.title', { + defaultMessage: 'Indexing by API', + })} +

+
+
+ ); +}; + +export const FlyoutBody: React.FC = () => { + const { engineName, engine } = useValues(EngineLogic); + const { apiKey } = engine as EngineDetails; + + const documentsApiUrl = getEnterpriseSearchUrl(`/api/as/v1/engines/${engineName}/documents`); + + return ( + + +

+ + documents API + + ), + clientLibrariesLink: ( + + client libraries + + ), + }} + /> +

+

+ {i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.api.example', { + defaultMessage: + 'To see the API in action, you can experiment with the example request below using a command line or a client library.', + })} +

+
+ + + POST + {documentsApiUrl} + + + {dedent(` + curl -X POST '${documentsApiUrl}' + -H 'Content-Type: application/json' + -H 'Authorization: Bearer ${apiKey}' + -d '${DOCUMENTS_API_JSON_EXAMPLE}' + # Returns + # [ + # { + # "id": "park_rocky-mountain", + # "errors": [] + # }, + # { + # "id": "park_saguaro", + # "errors": [] + # } + # ] + `)} + +
+ ); +}; + +export const FlyoutFooter: React.FC = () => { + const { closeDocumentCreation } = useActions(DocumentCreationLogic); + + return ( + + {FLYOUT_CANCEL_BUTTON} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/index.ts new file mode 100644 index 00000000000000..b9a6f2b3e750f6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ShowCreationModes } from './show_creation_modes'; +export { ApiCodeExample } from './api_code_example'; +export { PasteJsonText } from './paste_json_text'; +export { UploadJsonFile } from './upload_json_file'; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/table_link_flex_item.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.scss similarity index 57% rename from x-pack/plugins/apm/public/components/app/service_overview/table_link_flex_item.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.scss index 35df003af380d4..cca179e8c06080 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/table_link_flex_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.scss @@ -4,11 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexItem } from '@elastic/eui'; -import styled from 'styled-components'; - -export const TableLinkFlexItem = styled(EuiFlexItem)` - & > a { - text-align: right; - } -`; +.pasteJsonTextArea { + font-family: $euiCodeFontFamily; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.test.tsx new file mode 100644 index 00000000000000..50e4d473e5f78a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.test.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock'; +import { rerender } from '../../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; +import { EuiTextArea, EuiButtonEmpty, EuiButton } from '@elastic/eui'; + +import { PasteJsonText, FlyoutHeader, FlyoutBody, FlyoutFooter } from './paste_json_text'; + +describe('PasteJsonText', () => { + const values = { + textInput: 'hello world', + configuredLimits: { + engine: { + maxDocumentByteSize: 102400, + }, + }, + }; + const actions = { + setTextInput: jest.fn(), + closeDocumentCreation: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + }); + + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find(FlyoutHeader)).toHaveLength(1); + expect(wrapper.find(FlyoutBody)).toHaveLength(1); + expect(wrapper.find(FlyoutFooter)).toHaveLength(1); + }); + + describe('FlyoutHeader', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find('h2').text()).toEqual('Create documents'); + }); + }); + + describe('FlyoutBody', () => { + it('renders and updates the textarea value', () => { + setMockValues({ ...values, textInput: 'lorem ipsum' }); + const wrapper = shallow(); + const textarea = wrapper.find(EuiTextArea); + + expect(textarea.prop('value')).toEqual('lorem ipsum'); + + textarea.simulate('change', { target: { value: 'dolor sit amet' } }); + expect(actions.setTextInput).toHaveBeenCalledWith('dolor sit amet'); + }); + }); + + describe('FlyoutFooter', () => { + it('closes the modal', () => { + const wrapper = shallow(); + + wrapper.find(EuiButtonEmpty).simulate('click'); + expect(actions.closeDocumentCreation).toHaveBeenCalled(); + }); + + it('disables/enables the Continue button based on whether text has been entered', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(false); + + setMockValues({ ...values, textInput: '' }); + rerender(wrapper); + expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.tsx new file mode 100644 index 00000000000000..ad83e0eb1a286a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useValues, useActions } from 'kea'; + +import { i18n } from '@kbn/i18n'; +import { + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiButtonEmpty, + EuiTextArea, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +import { AppLogic } from '../../../app_logic'; + +import { FLYOUT_ARIA_LABEL_ID, FLYOUT_CANCEL_BUTTON, FLYOUT_CONTINUE_BUTTON } from '../constants'; +import { DocumentCreationLogic } from '../'; + +import './paste_json_text.scss'; + +export const PasteJsonText: React.FC = () => ( + <> + + + + +); + +export const FlyoutHeader: React.FC = () => { + return ( + + +

+ {i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.title', { + defaultMessage: 'Create documents', + })} +

+
+
+ ); +}; + +export const FlyoutBody: React.FC = () => { + const { configuredLimits } = useValues(AppLogic); + const maxDocumentByteSize = configuredLimits?.engine?.maxDocumentByteSize; + + const { textInput } = useValues(DocumentCreationLogic); + const { setTextInput } = useActions(DocumentCreationLogic); + + return ( + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.description', + { + defaultMessage: + 'Paste an array of JSON documents. Ensure the JSON is valid and that each document object is less than {maxDocumentByteSize} bytes.', + values: { maxDocumentByteSize }, + } + )} +

+
+ + setTextInput(e.target.value)} + aria-label={i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.label', + { defaultMessage: 'Paste JSON here' } + )} + className="pasteJsonTextArea" + fullWidth + rows={12} + /> +
+ ); +}; + +export const FlyoutFooter: React.FC = () => { + const { textInput } = useValues(DocumentCreationLogic); + const { closeDocumentCreation } = useActions(DocumentCreationLogic); + + return ( + + + + {FLYOUT_CANCEL_BUTTON} + + + + {FLYOUT_CONTINUE_BUTTON} + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.test.tsx new file mode 100644 index 00000000000000..d02545625345d9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { setMockActions } from '../../../../__mocks__/kea.mock'; + +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { EuiButtonEmpty } from '@elastic/eui'; + +import { DocumentCreationButtons } from '../'; +import { ShowCreationModes } from './'; + +describe('ShowCreationModes', () => { + const actions = { + closeDocumentCreation: jest.fn(), + }; + let wrapper: ShallowWrapper; + + beforeAll(() => { + jest.clearAllMocks(); + setMockActions(actions); + wrapper = shallow(); + }); + + it('renders', () => { + expect(wrapper.find('h2').text()).toEqual('Add new documents'); + expect(wrapper.find(DocumentCreationButtons)).toHaveLength(1); + }); + + it('closes the flyout', () => { + wrapper.find(EuiButtonEmpty).simulate('click'); + expect(actions.closeDocumentCreation).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.tsx new file mode 100644 index 00000000000000..f923661a57bcc1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useActions } from 'kea'; + +import { i18n } from '@kbn/i18n'; +import { + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiButtonEmpty, +} from '@elastic/eui'; + +import { FLYOUT_ARIA_LABEL_ID, FLYOUT_CANCEL_BUTTON } from '../constants'; +import { DocumentCreationLogic, DocumentCreationButtons } from '../'; + +export const ShowCreationModes: React.FC = () => { + const { closeDocumentCreation } = useActions(DocumentCreationLogic); + + return ( + <> + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.showCreationModes.title', + { defaultMessage: 'Add new documents' } + )} +

+
+
+ + + + + {FLYOUT_CANCEL_BUTTON} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.test.tsx new file mode 100644 index 00000000000000..72a245df817ba9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.test.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock'; +import { rerender } from '../../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; +import { EuiFilePicker, EuiButtonEmpty, EuiButton } from '@elastic/eui'; + +import { UploadJsonFile, FlyoutHeader, FlyoutBody, FlyoutFooter } from './upload_json_file'; + +describe('UploadJsonFile', () => { + const mockFile = new File(['mock'], 'mock.json', { type: 'application/json' }); + const values = { + fileInput: null, + configuredLimits: { + engine: { + maxDocumentByteSize: 102400, + }, + }, + }; + const actions = { + setFileInput: jest.fn(), + closeDocumentCreation: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + }); + + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find(FlyoutHeader)).toHaveLength(1); + expect(wrapper.find(FlyoutBody)).toHaveLength(1); + expect(wrapper.find(FlyoutFooter)).toHaveLength(1); + }); + + describe('FlyoutHeader', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find('h2').text()).toEqual('Drag and drop .json'); + }); + }); + + describe('FlyoutBody', () => { + it('updates fileInput when files are added & removed', () => { + const wrapper = shallow(); + + wrapper.find(EuiFilePicker).simulate('change', [mockFile]); + expect(actions.setFileInput).toHaveBeenCalledWith(mockFile); + + wrapper.find(EuiFilePicker).simulate('change', []); + expect(actions.setFileInput).toHaveBeenCalledWith(null); + }); + }); + + describe('FlyoutFooter', () => { + it('closes the flyout', () => { + const wrapper = shallow(); + + wrapper.find(EuiButtonEmpty).simulate('click'); + expect(actions.closeDocumentCreation).toHaveBeenCalled(); + }); + + it('disables/enables the Continue button based on whether files have been uploaded', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(true); + + setMockValues({ ...values, fineInput: mockFile }); + rerender(wrapper); + expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.tsx new file mode 100644 index 00000000000000..6c5b1de79c3207 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.tsx @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useValues, useActions } from 'kea'; + +import { i18n } from '@kbn/i18n'; +import { + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiButtonEmpty, + EuiFilePicker, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +import { AppLogic } from '../../../app_logic'; + +import { FLYOUT_ARIA_LABEL_ID, FLYOUT_CANCEL_BUTTON, FLYOUT_CONTINUE_BUTTON } from '../constants'; +import { DocumentCreationLogic } from '../'; + +export const UploadJsonFile: React.FC = () => ( + <> + + + + +); + +export const FlyoutHeader: React.FC = () => { + return ( + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.uploadJsonFile.title', + { defaultMessage: 'Drag and drop .json' } + )} +

+
+
+ ); +}; + +export const FlyoutBody: React.FC = () => { + const { configuredLimits } = useValues(AppLogic); + const maxDocumentByteSize = configuredLimits?.engine?.maxDocumentByteSize; + + const { setFileInput } = useActions(DocumentCreationLogic); + + return ( + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.uploadJsonFile.label', + { + defaultMessage: + 'If you have a .json file, drag and drop or upload it. Ensure the JSON is valid and that each document object is less than {maxDocumentByteSize} bytes.', + values: { maxDocumentByteSize }, + } + )} +

+
+ + setFileInput(files?.length ? files[0] : null)} + accept="application/json" + fullWidth + /> +
+ ); +}; + +export const FlyoutFooter: React.FC = () => { + const { fileInput } = useValues(DocumentCreationLogic); + const { closeDocumentCreation } = useActions(DocumentCreationLogic); + + return ( + + + + {FLYOUT_CANCEL_BUTTON} + + + + {FLYOUT_CONTINUE_BUTTON} + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx index 8bd473c003eb12..93aff04b3f7c07 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx @@ -16,8 +16,6 @@ import { DocumentCreationButtons } from './'; describe('DocumentCreationButtons', () => { const values = { engineName: 'test-engine', - isSampleEngine: false, - myRole: { canViewEngineCrawler: true }, }; const actions = { openDocumentCreation: jest.fn(), @@ -43,7 +41,7 @@ describe('DocumentCreationButtons', () => { expect(wrapper.find(EuiCardTo).prop('isDisabled')).toEqual(true); }); - it('opens the DocumentCreationModal on click', () => { + it('opens the DocumentCreationFlyout on click', () => { const wrapper = shallow(); wrapper.find(EuiCard).at(0).simulate('click'); @@ -56,25 +54,9 @@ describe('DocumentCreationButtons', () => { expect(actions.openDocumentCreation).toHaveBeenCalledWith('api'); }); - describe('crawler card', () => { - it('renders the crawler button with a link to the crawler page', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiCardTo).prop('to')).toEqual('/engines/test-engine/crawler'); - }); - - it('does not render the crawler button if the user does not have access', () => { - setMockValues({ ...values, myRole: { canViewEngineCrawler: false } }); - const wrapper = shallow(); - - expect(wrapper.find(EuiCardTo)).toHaveLength(0); - }); - - it('does not render the crawler button for the sample engine', () => { - setMockValues({ ...values, isSampleEngine: true }); - const wrapper = shallow(); + it('renders the crawler button with a link to the crawler page', () => { + const wrapper = shallow(); - expect(wrapper.find(EuiCardTo)).toHaveLength(0); - }); + expect(wrapper.find(EuiCardTo).prop('to')).toEqual('/engines/test-engine/crawler'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx index edeae5205b6467..ce7cae56783382 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx @@ -22,7 +22,6 @@ import { import { EuiCardTo } from '../../../shared/react_router_helpers'; import { DOCS_PREFIX, getEngineRoute, ENGINE_CRAWLER_PATH } from '../../routes'; -import { AppLogic } from '../../app_logic'; import { EngineLogic } from '../engine'; import { DocumentCreationLogic } from './'; @@ -34,11 +33,7 @@ interface Props { export const DocumentCreationButtons: React.FC = ({ disabled = false }) => { const { openDocumentCreation } = useActions(DocumentCreationLogic); - const { engineName, isSampleEngine } = useValues(EngineLogic); - const { - myRole: { canViewEngineCrawler }, - } = useValues(AppLogic); - const showCrawlerLink = canViewEngineCrawler && !isSampleEngine; + const { engineName } = useValues(EngineLogic); const crawlerLink = getEngineRoute(engineName) + ENGINE_CRAWLER_PATH; return ( @@ -61,7 +56,7 @@ export const DocumentCreationButtons: React.FC = ({ disabled = false }) =

- + = ({ disabled = false }) = isDisabled={disabled} /> - {showCrawlerLink && ( - - } - betaBadgeLabel={i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.buttons.betaTitle', - { defaultMessage: 'Beta' } - )} - betaBadgeTooltipContent={i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.buttons.betaTooltip', - { - defaultMessage: - 'The Elastic Crawler is not GA. Please help us by reporting any bugs.', - } - )} - to={crawlerLink} - isDisabled={disabled} - /> - - )} + + } + betaBadgeLabel={i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.buttons.betaTitle', + { defaultMessage: 'Beta' } + )} + betaBadgeTooltipContent={i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.buttons.betaTooltip', + { + defaultMessage: + 'The Elastic Crawler is not GA. Please help us by reporting any bugs.', + } + )} + to={crawlerLink} + isDisabled={disabled} + /> + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.test.tsx similarity index 55% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.test.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.test.tsx index a00aed96a6fbcb..f2799cde41e971 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.test.tsx @@ -8,12 +8,19 @@ import { setMockValues, setMockActions } from '../../../__mocks__/kea.mock'; import React from 'react'; import { shallow } from 'enzyme'; -import { EuiModal, EuiModalBody } from '@elastic/eui'; - +import { EuiFlyout } from '@elastic/eui'; + +import { + ShowCreationModes, + ApiCodeExample, + PasteJsonText, + UploadJsonFile, +} from './creation_mode_components'; import { DocumentCreationStep } from './types'; -import { DocumentCreationModal, DocumentCreationButtons } from './'; -describe('DocumentCreationModal', () => { +import { DocumentCreationFlyout, FlyoutContent } from './document_creation_flyout'; + +describe('DocumentCreationFlyout', () => { const values = { isDocumentCreationOpen: true, creationMode: 'text', @@ -29,73 +36,73 @@ describe('DocumentCreationModal', () => { setMockActions(actions); }); - it('renders a closeable modal', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiModal)).toHaveLength(1); + it('renders a closeable flyout', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiFlyout)).toHaveLength(1); - wrapper.find(EuiModal).prop('onClose')(); + wrapper.find(EuiFlyout).prop('onClose')(); expect(actions.closeDocumentCreation).toHaveBeenCalled(); }); it('does not render if isDocumentCreationOpen is false', () => { setMockValues({ ...values, isDocumentCreationOpen: false }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.isEmptyRender()).toBe(true); }); - describe('modal content', () => { - it('renders document creation mode buttons', () => { + describe('FlyoutContent', () => { + it('renders ShowCreationModes', () => { setMockValues({ ...values, creationStep: DocumentCreationStep.ShowCreationModes }); - const wrapper = shallow(); + const wrapper = shallow(); - expect(wrapper.find(DocumentCreationButtons)).toHaveLength(1); + expect(wrapper.find(ShowCreationModes)).toHaveLength(1); }); describe('creation modes', () => { it('renders ApiCodeExample', () => { setMockValues({ ...values, creationMode: 'api' }); - const wrapper = shallow(); + const wrapper = shallow(); - expect(wrapper.find(EuiModalBody).dive().text()).toBe('ApiCodeExample'); // TODO: actual component + expect(wrapper.find(ApiCodeExample)).toHaveLength(1); }); it('renders PasteJsonText', () => { setMockValues({ ...values, creationMode: 'text' }); - const wrapper = shallow(); + const wrapper = shallow(); - expect(wrapper.find(EuiModalBody).dive().text()).toBe('PasteJsonText'); // TODO: actual component + expect(wrapper.find(PasteJsonText)).toHaveLength(1); }); it('renders UploadJsonFile', () => { setMockValues({ ...values, creationMode: 'file' }); - const wrapper = shallow(); + const wrapper = shallow(); - expect(wrapper.find(EuiModalBody).dive().text()).toBe('UploadJsonFile'); // TODO: actual component + expect(wrapper.find(UploadJsonFile)).toHaveLength(1); }); }); describe('creation steps', () => { it('renders an error page', () => { setMockValues({ ...values, creationStep: DocumentCreationStep.ShowError }); - const wrapper = shallow(); + const wrapper = shallow(); - expect(wrapper.find(EuiModalBody).dive().text()).toBe('DocumentCreationError'); // TODO: actual component + expect(wrapper.text()).toBe('DocumentCreationError'); // TODO: actual component }); it('renders an error summary', () => { setMockValues({ ...values, creationStep: DocumentCreationStep.ShowErrorSummary }); - const wrapper = shallow(); + const wrapper = shallow(); - expect(wrapper.find(EuiModalBody).dive().text()).toBe('DocumentCreationSummary'); // TODO: actual component + expect(wrapper.text()).toBe('DocumentCreationSummary'); // TODO: actual component }); it('renders a success summary', () => { setMockValues({ ...values, creationStep: DocumentCreationStep.ShowSuccessSummary }); - const wrapper = shallow(); + const wrapper = shallow(); // TODO: Figure out if the error and success summary should remain the same vs different components - expect(wrapper.find(EuiModalBody).dive().text()).toBe('DocumentCreationSummary'); // TODO: actual component + expect(wrapper.text()).toBe('DocumentCreationSummary'); // TODO: actual component }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.tsx new file mode 100644 index 00000000000000..ca52d14befb380 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useValues, useActions } from 'kea'; + +import { EuiPortal, EuiFlyout } from '@elastic/eui'; + +import { DocumentCreationLogic } from './'; +import { DocumentCreationStep } from './types'; +import { FLYOUT_ARIA_LABEL_ID } from './constants'; + +import { + ShowCreationModes, + ApiCodeExample, + PasteJsonText, + UploadJsonFile, +} from './creation_mode_components'; + +export const DocumentCreationFlyout: React.FC = () => { + const { closeDocumentCreation } = useActions(DocumentCreationLogic); + const { isDocumentCreationOpen } = useValues(DocumentCreationLogic); + + return isDocumentCreationOpen ? ( + + + + + + ) : null; +}; + +export const FlyoutContent: React.FC = () => { + const { creationStep, creationMode } = useValues(DocumentCreationLogic); + + switch (creationStep) { + case DocumentCreationStep.ShowCreationModes: + return ; + case DocumentCreationStep.AddDocuments: + switch (creationMode) { + case 'api': + return ; + case 'text': + return ; + case 'file': + return ; + } + case DocumentCreationStep.ShowError: + return <>DocumentCreationError; + case DocumentCreationStep.ShowErrorSummary: + return <>DocumentCreationSummary; + case DocumentCreationStep.ShowSuccessSummary: + return <>DocumentCreationSummary; + } +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts index ff38ab5add3672..1145d7853cb1a1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts @@ -5,7 +5,9 @@ */ import { resetContext } from 'kea'; +import dedent from 'dedent'; +import { DOCUMENTS_API_JSON_EXAMPLE } from './constants'; import { DocumentCreationStep } from './types'; import { DocumentCreationLogic } from './'; @@ -14,7 +16,10 @@ describe('DocumentCreationLogic', () => { isDocumentCreationOpen: false, creationMode: 'text', creationStep: DocumentCreationStep.AddDocuments, + textInput: dedent(DOCUMENTS_API_JSON_EXAMPLE), + fileInput: null, }; + const mockFile = new File(['mockFile'], 'mockFile.json'); const mount = () => { resetContext({}); @@ -130,5 +135,33 @@ describe('DocumentCreationLogic', () => { }); }); }); + + describe('setTextInput', () => { + describe('textInput', () => { + it('should be set to the provided value', () => { + mount(); + DocumentCreationLogic.actions.setTextInput('hello world'); + + expect(DocumentCreationLogic.values).toEqual({ + ...DEFAULT_VALUES, + textInput: 'hello world', + }); + }); + }); + }); + + describe('setFileInput', () => { + describe('fileInput', () => { + it('should be set to the provided value', () => { + mount(); + DocumentCreationLogic.actions.setFileInput(mockFile); + + expect(DocumentCreationLogic.values).toEqual({ + ...DEFAULT_VALUES, + fileInput: mockFile, + }); + }); + }); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts index 26f7a1f3d50ece..5b85e7f2ab54e1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts @@ -5,13 +5,17 @@ */ import { kea, MakeLogicType } from 'kea'; +import dedent from 'dedent'; +import { DOCUMENTS_API_JSON_EXAMPLE } from './constants'; import { DocumentCreationMode, DocumentCreationStep } from './types'; interface DocumentCreationValues { isDocumentCreationOpen: boolean; creationMode: DocumentCreationMode; creationStep: DocumentCreationStep; + textInput: string; + fileInput: File | null; } interface DocumentCreationActions { @@ -19,17 +23,21 @@ interface DocumentCreationActions { openDocumentCreation(creationMode: DocumentCreationMode): { creationMode: DocumentCreationMode }; closeDocumentCreation(): void; setCreationStep(creationStep: DocumentCreationStep): { creationStep: DocumentCreationStep }; + setTextInput(textInput: string): { textInput: string }; + setFileInput(fileInput: File | null): { fileInput: File | null }; } export const DocumentCreationLogic = kea< MakeLogicType >({ - path: ['enterprise_search', 'app_search', 'document_creation_modal_logic'], + path: ['enterprise_search', 'app_search', 'document_creation_logic'], actions: () => ({ showCreationModes: () => null, openDocumentCreation: (creationMode) => ({ creationMode }), closeDocumentCreation: () => null, setCreationStep: (creationStep) => ({ creationStep }), + setTextInput: (textInput) => ({ textInput }), + setFileInput: (fileInput) => ({ fileInput }), }), reducers: () => ({ isDocumentCreationOpen: [ @@ -54,5 +62,17 @@ export const DocumentCreationLogic = kea< setCreationStep: (_, { creationStep }) => creationStep, }, ], + textInput: [ + dedent(DOCUMENTS_API_JSON_EXAMPLE), + { + setTextInput: (_, { textInput }) => textInput, + }, + ], + fileInput: [ + null, + { + setFileInput: (_, { fileInput }) => fileInput, + }, + ], }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.tsx deleted file mode 100644 index 95ce5456ef9a83..00000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { useValues, useActions } from 'kea'; - -import { i18n } from '@kbn/i18n'; -import { - EuiOverlayMask, - EuiModal, - EuiModalHeader, - EuiModalHeaderTitle, - EuiModalBody, - EuiModalFooter, -} from '@elastic/eui'; - -import { DocumentCreationLogic, DocumentCreationButtons } from './'; -import { DocumentCreationStep } from './types'; - -export const DocumentCreationModal: React.FC = () => { - const { closeDocumentCreation } = useActions(DocumentCreationLogic); - const { isDocumentCreationOpen, creationMode, creationStep } = useValues(DocumentCreationLogic); - - if (!isDocumentCreationOpen) return null; - - return ( - - - - - {i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.modalTitle', { - defaultMessage: 'Document Import', - })} - - - - {creationStep === DocumentCreationStep.ShowError && <>DocumentCreationError} - {creationStep === DocumentCreationStep.ShowCreationModes && } - {creationStep === DocumentCreationStep.AddDocuments && creationMode === 'api' && ( - <>ApiCodeExample - )} - {creationStep === DocumentCreationStep.AddDocuments && creationMode === 'text' && ( - <>PasteJsonText - )} - {creationStep === DocumentCreationStep.AddDocuments && creationMode === 'file' && ( - <>UploadJsonFile - )} - {creationStep === DocumentCreationStep.ShowErrorSummary && <>DocumentCreationSummary} - {creationStep === DocumentCreationStep.ShowSuccessSummary && <>DocumentCreationSummary} - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/index.ts index 0f4eaaeda0e1a2..d443b02393c057 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/index.ts @@ -5,5 +5,5 @@ */ export { DocumentCreationButtons } from './document_creation_buttons'; -export { DocumentCreationModal } from './document_creation_modal'; +export { DocumentCreationFlyout } from './document_creation_flyout'; export { DocumentCreationLogic } from './document_creation_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.test.tsx index 17e6e2538f044b..52fa0d03a9719d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import { EuiButton } from '@elastic/eui'; -import { DocumentCreationModal } from '../document_creation'; +import { DocumentCreationFlyout } from '../document_creation'; import { DocumentCreationButton } from './document_creation_button'; describe('DocumentCreationButton', () => { @@ -24,7 +24,7 @@ describe('DocumentCreationButton', () => { it('renders', () => { expect(wrapper.find(EuiButton).length).toEqual(1); - expect(wrapper.find(DocumentCreationModal).length).toEqual(1); + expect(wrapper.find(DocumentCreationFlyout).length).toEqual(1); }); it('opens the document creation modes modal on click', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.tsx index 6d211cf45ca9f3..3e4039bafcac73 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.tsx @@ -10,7 +10,7 @@ import { useActions } from 'kea'; import { i18n } from '@kbn/i18n'; import { EuiButton } from '@elastic/eui'; -import { DocumentCreationLogic, DocumentCreationModal } from '../document_creation'; +import { DocumentCreationLogic, DocumentCreationFlyout } from '../document_creation'; export const DocumentCreationButton: React.FC = () => { const { showCreationModes } = useActions(DocumentCreationLogic); @@ -27,7 +27,7 @@ export const DocumentCreationButton: React.FC = () => { defaultMessage: 'Index documents', })} - + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts new file mode 100644 index 00000000000000..dd52f6b8227ba6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SchemaTypes } from '../../../../shared/types'; + +import { buildSearchUIConfig } from './build_search_ui_config'; + +describe('buildSearchUIConfig', () => { + it('builds a configuration object for Search UI', () => { + const connector = {}; + const schema = { + foo: 'text' as SchemaTypes, + bar: 'number' as SchemaTypes, + }; + + const config = buildSearchUIConfig(connector, schema); + expect(config.apiConnector).toEqual(connector); + expect(config.searchQuery.result_fields).toEqual({ + bar: { + raw: {}, + snippet: { + fallback: true, + size: 300, + }, + }, + foo: { + raw: {}, + snippet: { + fallback: true, + size: 300, + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts new file mode 100644 index 00000000000000..533adbaf5bab93 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Schema } from '../../../../shared/types'; + +export const buildSearchUIConfig = (apiConnector: object, schema: Schema) => { + return { + alwaysSearchOnInitialLoad: true, + apiConnector, + trackUrlState: false, + initialState: { + sortDirection: 'desc', + sortField: 'id', + }, + searchQuery: { + result_fields: Object.keys(schema || {}).reduce( + (acc: { [key: string]: object }, key: string) => { + acc[key] = { + snippet: { + size: 300, + fallback: true, + }, + raw: {}, + }; + return acc; + }, + {} + ), + }, + }; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx index 49cc573b686bc2..1501efc589fc08 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx @@ -20,6 +20,7 @@ import { externalUrl } from '../../../../shared/enterprise_search_url'; import { SearchBoxView, SortingView } from './views'; import { SearchExperienceContent } from './search_experience_content'; +import { buildSearchUIConfig } from './build_search_ui_config'; const DEFAULT_SORT_OPTIONS = [ { @@ -52,15 +53,7 @@ export const SearchExperience: React.FC = () => { searchKey: engine.apiKey, }); - const searchProviderConfig = { - alwaysSearchOnInitialLoad: true, - apiConnector: connector, - trackUrlState: false, - initialState: { - sortDirection: 'desc', - sortField: 'id', - }, - }; + const searchProviderConfig = buildSearchUIConfig(connector, engine.schema || {}); return (
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx index 455e237848a4bc..a46ec560a13e0a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx @@ -15,6 +15,7 @@ import { Results } from '@elastic/react-search-ui'; import { ResultView } from './views'; import { Pagination } from './pagination'; +import { SchemaTypes } from '../../../../shared/types'; import { SearchExperienceContent } from './search_experience_content'; describe('SearchExperienceContent', () => { @@ -27,6 +28,11 @@ describe('SearchExperienceContent', () => { engineName: 'engine1', isMetaEngine: false, myRole: { canManageEngineDocuments: true }, + engine: { + schema: { + title: 'string' as SchemaTypes, + }, + }, }; beforeEach(() => { @@ -40,7 +46,7 @@ describe('SearchExperienceContent', () => { expect(wrapper.isEmptyRender()).toBe(false); }); - it('passes engineName to the result view', () => { + it('passes engineName and schema to the result view', () => { const props = { result: { id: { @@ -56,6 +62,9 @@ describe('SearchExperienceContent', () => { raw: 'bar', }, }, + schemaForTypeHighlights: { + title: 'string' as SchemaTypes, + }, }; const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx index 9194a3a1db5e44..55a8377261dd9b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx @@ -25,7 +25,7 @@ export const SearchExperienceContent: React.FC = () => { const { resultSearchTerm, totalResults, wasSearched } = useSearchContextState(); const { myRole } = useValues(AppLogic); - const { isMetaEngine } = useValues(EngineLogic); + const { isMetaEngine, engine } = useValues(EngineLogic); if (!wasSearched) return null; @@ -44,7 +44,7 @@ export const SearchExperienceContent: React.FC = () => { { - return ; + return ; }} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx index 049a3ad1bed66f..91334f312623df 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { ResultView } from '.'; +import { SchemaTypes } from '../../../../../shared/types'; import { Result } from '../../../result/result'; describe('ResultView', () => { @@ -27,8 +28,16 @@ describe('ResultView', () => { }, }; + const schema = { + title: 'string' as SchemaTypes, + }; + it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(Result).exists()).toBe(true); + const wrapper = shallow(); + expect(wrapper.find(Result).props()).toEqual({ + result, + shouldLinkToDetailPage: true, + schemaForTypeHighlights: schema, + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx index 52b845a1aee2d8..543c63b3349407 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx @@ -7,16 +7,22 @@ import React from 'react'; import { Result as ResultType } from '../../../result/types'; +import { Schema } from '../../../../../shared/types'; import { Result } from '../../../result/result'; export interface Props { result: ResultType; + schemaForTypeHighlights?: Schema; } -export const ResultView: React.FC = ({ result }) => { +export const ResultView: React.FC = ({ result, schemaForTypeHighlights }) => { return (
  • - +
  • ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx index 0d2ce654d4a0ab..2e419168f2e1bb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx @@ -102,12 +102,6 @@ describe('EngineNav', () => { const wrapper = shallow(); expect(wrapper.find('[data-test-subj="EngineCrawlerLink"]')).toHaveLength(0); }); - - it('does not render for sample engine', () => { - setMockValues({ ...values, myRole, isSampleEngine: true }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineCrawlerLink"]')).toHaveLength(0); - }); }); describe('meta engine source engines link', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx index 35389bbe4b3ba7..0fed7cd0fc8fc6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx @@ -153,7 +153,7 @@ export const EngineNav: React.FC = () => { )} - {canViewEngineCrawler && !isMetaEngine && !isSampleEngine && ( + {canViewEngineCrawler && !isMetaEngine && ( { @@ -32,6 +32,6 @@ describe('EmptyEngineOverview', () => { it('renders document creation components', () => { expect(wrapper.find(DocumentCreationButtons)).toHaveLength(1); - expect(wrapper.find(DocumentCreationModal)).toHaveLength(1); + expect(wrapper.find(DocumentCreationFlyout)).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx index d65ca4868d2828..83dd396e5e0801 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx @@ -16,7 +16,7 @@ import { } from '@elastic/eui'; import { DOCS_PREFIX } from '../../routes'; -import { DocumentCreationButtons, DocumentCreationModal } from '../document_creation'; +import { DocumentCreationButtons, DocumentCreationFlyout } from '../document_creation'; export const EmptyEngineOverview: React.FC = () => { return ( @@ -42,7 +42,7 @@ export const EmptyEngineOverview: React.FC = () => { - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx index 66c0cc165fc056..1b222cfaacf7c1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx @@ -15,6 +15,7 @@ import { import React from 'react'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { Schema } from '../../../shared/types'; import { Result } from '../result/result'; export const Library: React.FC = () => { @@ -35,12 +36,18 @@ export const Library: React.FC = () => { description: { raw: 'A description', }, - states: { - raw: ['Pennsylvania', 'Ohio'], + date_established: { + raw: '1968-10-02T05:00:00Z', + }, + location: { + raw: '37.3,-113.05', }, visitors: { raw: 1000, }, + states: { + raw: ['Pennsylvania', 'Ohio'], + }, size: { raw: 200, }, @@ -50,6 +57,17 @@ export const Library: React.FC = () => { }, }; + const schema: Schema = { + title: 'text', + description: 'text', + date_established: 'date', + location: 'geolocation', + states: 'text', + visitors: 'number', + size: 'number', + length: 'number', + }; + return ( <> @@ -170,6 +188,21 @@ export const Library: React.FC = () => { }, }} /> + + + +

    With a link

    +
    + + + + + + +

    With field value type highlights

    +
    + + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss index ed8ce512a2eb80..8342061ee00c3b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss @@ -5,6 +5,7 @@ width: 100%; padding: $euiSize; overflow: hidden; + color: $euiTextColor; } &__hiddenFieldsIndicator { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx index ade26551039faa..5b598a0b8565ec 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx @@ -11,6 +11,9 @@ import { EuiPanel } from '@elastic/eui'; import { ResultField } from './result_field'; import { ResultHeader } from './result_header'; +import { ReactRouterHelper } from '../../../shared/react_router_helpers/eui_components'; +import { SchemaTypes } from '../../../shared/types'; + import { Result } from './result'; describe('Result', () => { @@ -37,6 +40,12 @@ describe('Result', () => { }, }; + const schema = { + title: 'text' as SchemaTypes, + description: 'text' as SchemaTypes, + length: 'number' as SchemaTypes, + }; + it('renders', () => { const wrapper = shallow(); expect(wrapper.find(EuiPanel).exists()).toBe(true); @@ -62,6 +71,33 @@ describe('Result', () => { }); }); + describe('document detail link', () => { + it('will render a link if shouldLinkToDetailPage is true', () => { + const wrapper = shallow(); + expect(wrapper.find(ReactRouterHelper).prop('to')).toEqual('/engines/my-engine/documents/1'); + expect(wrapper.find('article.appSearchResult__content').exists()).toBe(false); + expect(wrapper.find('a.appSearchResult__content').exists()).toBe(true); + }); + + it('will not render a link if shouldLinkToDetailPage is not set', () => { + const wrapper = shallow(); + expect(wrapper.find(ReactRouterHelper).exists()).toBe(false); + expect(wrapper.find('article.appSearchResult__content').exists()).toBe(true); + expect(wrapper.find('a.appSearchResult__content').exists()).toBe(false); + }); + }); + + it('will render field details with type highlights if schemaForTypeHighlights has been provided', () => { + const wrapper = shallow( + + ); + expect(wrapper.find(ResultField).map((rf) => rf.prop('type'))).toEqual([ + 'text', + 'text', + 'number', + ]); + }); + describe('when there are more than 5 fields', () => { const propsWithMoreFields = { result: { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx index 4f343e64b12ae5..11415f55123804 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx @@ -14,15 +14,25 @@ import { i18n } from '@kbn/i18n'; import { FieldValue, Result as ResultType } from './types'; import { ResultField } from './result_field'; import { ResultHeader } from './result_header'; +import { getDocumentDetailRoute } from '../../routes'; +import { ReactRouterHelper } from '../../../shared/react_router_helpers/eui_components'; +import { Schema } from '../../../shared/types'; interface Props { result: ResultType; showScore?: boolean; + shouldLinkToDetailPage?: boolean; + schemaForTypeHighlights?: Schema; } const RESULT_CUTOFF = 5; -export const Result: React.FC = ({ result, showScore }) => { +export const Result: React.FC = ({ + result, + showScore = false, + shouldLinkToDetailPage = false, + schemaForTypeHighlights, +}) => { const [isOpen, setIsOpen] = useState(false); const ID = 'id'; @@ -33,6 +43,19 @@ export const Result: React.FC = ({ result, showScore }) => { [result] ); const numResults = resultFields.length; + const typeForField = (fieldName: string) => { + if (schemaForTypeHighlights) return schemaForTypeHighlights[fieldName]; + }; + + const conditionallyLinkedArticle = (children: React.ReactNode) => { + return shouldLinkToDetailPage ? ( + +
    {children} + + ) : ( +
    {children}
    + ); + }; return ( = ({ result, showScore }) => { defaultMessage: 'View document details', })} > -
    - -
    - {resultFields - .slice(0, isOpen ? resultFields.length : RESULT_CUTOFF) - .map(([field, value]: [string, FieldValue]) => ( - - ))} -
    - {numResults > RESULT_CUTOFF && !isOpen && ( -
    - {i18n.translate('xpack.enterpriseSearch.appSearch.result.numberOfAdditionalFields', { - defaultMessage: '{numberOfAdditionalFields} more fields', - values: { - numberOfAdditionalFields: numResults - RESULT_CUTOFF, - }, - })} -
    - )} -
    + {conditionallyLinkedArticle( + <> + +
    + {resultFields + .slice(0, isOpen ? resultFields.length : RESULT_CUTOFF) + .map(([field, value]: [string, FieldValue]) => ( + + ))} +
    + {numResults > RESULT_CUTOFF && !isOpen && ( +
    + {i18n.translate('xpack.enterpriseSearch.appSearch.result.numberOfAdditionalFields', { + defaultMessage: '{numberOfAdditionalFields} more fields', + values: { + numberOfAdditionalFields: numResults - RESULT_CUTOFF, + }, + })} +
    + )} + + )} {numResults > RESULT_CUTOFF && (
    ); + const { code } = status.state.state.ui.message; const accordion = ( + {code?.length ? ( + + {code} + + ) : null} = (props: Props) => { paddingLeft: `0.5rem`, }} > - {(status.state.state.ui.message.nextSteps || []).map((step: AlertMessage) => { - return {}} label={replaceTokens(step)} />; - })} + {(status.state.state.ui.message.nextSteps || []).map( + (step: AlertMessage, stepIndex: number) => { + return ( + {}} + label={replaceTokens(step)} + key={index + stepIndex} + /> + ); + } + )} } + label={} /> diff --git a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx new file mode 100644 index 00000000000000..4d22d422ecda6f --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { Expression, Props } from '../components/duration/expression'; +import { AlertTypeModel, ValidationResult } from '../../../../triggers_actions_ui/public'; +import { ALERT_CCR_READ_EXCEPTIONS, ALERT_DETAILS } from '../../../common/constants'; + +interface ValidateOptions { + duration: string; +} + +const validate = (inputValues: ValidateOptions): ValidationResult => { + const validationResult = { errors: {} }; + const errors: { [key: string]: string[] } = { + duration: [], + }; + if (!inputValues.duration) { + errors.duration.push( + i18n.translate('xpack.monitoring.alerts.validation.duration', { + defaultMessage: 'A valid duration is required.', + }) + ); + } + validationResult.errors = errors; + return validationResult; +}; + +export function createCCRReadExceptionsAlertType(): AlertTypeModel { + return { + id: ALERT_CCR_READ_EXCEPTIONS, + description: ALERT_DETAILS[ALERT_CCR_READ_EXCEPTIONS].description, + iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/kibana-alerts.html`; + }, + alertParamsExpression: (props: Props) => ( + + ), + validate, + defaultActionMessage: '{{context.internalFullMessage}}', + requiresAppContext: true, + }; +} diff --git a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx index 5054c47245f0fc..1fe40fc8777f4b 100644 --- a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx @@ -13,7 +13,6 @@ import { Expression, Props } from '../components/duration/expression'; export function createCpuUsageAlertType(): AlertTypeModel { return { id: ALERT_CPU_USAGE, - name: ALERT_DETAILS[ALERT_CPU_USAGE].label, description: ALERT_DETAILS[ALERT_CPU_USAGE].description, iconClass: 'bell', documentationUrl(docLinks) { diff --git a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx index 00b70658e4289a..5579b8e1275a39 100644 --- a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx @@ -15,7 +15,6 @@ import { ALERT_DISK_USAGE, ALERT_DETAILS } from '../../../common/constants'; export function createDiskUsageAlertType(): AlertTypeModel { return { id: ALERT_DISK_USAGE, - name: ALERT_DETAILS[ALERT_DISK_USAGE].label, description: ALERT_DETAILS[ALERT_DISK_USAGE].description, iconClass: 'bell', documentationUrl(docLinks) { diff --git a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx index c8d0a7a5d49f2a..d50e9c3a5c2828 100644 --- a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx @@ -15,7 +15,6 @@ export function createLegacyAlertTypes(): AlertTypeModel[] { return LEGACY_ALERTS.map((legacyAlert) => { return { id: legacyAlert, - name: LEGACY_ALERT_DETAILS[legacyAlert].label, description: LEGACY_ALERT_DETAILS[legacyAlert].description, iconClass: 'bell', documentationUrl(docLinks) { diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx index 82a1a1f841a22a..bbea32e4d2d04a 100644 --- a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx +++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx @@ -171,6 +171,7 @@ export function getAlertPanelsByCategory( for (const { alert, states } of category.alerts) { const items = []; for (const alertState of states.filter(({ state }) => stateFilter(state))) { + const { nodeName, itemLabel } = alertState.state; items.push({ name: ( @@ -188,7 +189,7 @@ export function getAlertPanelsByCategory( )} - {alertState.state.nodeName} + {nodeName || itemLabel} ), panel: ++tertiaryPanelIndex, diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx index c48706f4edcb97..735b9c3637cdd9 100644 --- a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx +++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx @@ -69,10 +69,11 @@ export function getAlertPanelsByNode( const states = (statesByNodes[nodeUuid] as CommonAlertState[]).filter(({ state }) => stateFilter(state) ); + const { nodeName, itemLabel } = states[0].state; return { name: ( - {states[0].state.nodeName} ({states.length}) + {nodeName || itemLabel} ({states.length}) ), panel: index + 1, @@ -86,7 +87,8 @@ export function getAlertPanelsByNode( let title = ''; for (const { alert, states } of alertsForNode) { for (const alertState of states) { - title = alertState.state.nodeName; + const { nodeName, itemLabel } = alertState.state; + title = nodeName || itemLabel; panelItems.push({ name: ( diff --git a/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx b/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx index b8ac69cbae68a4..0ddda96a1100d8 100644 --- a/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx +++ b/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx @@ -77,6 +77,7 @@ export function replaceTokens(alertMessage: AlertMessage): JSX.Element | string } const url = linkToken.partialUrl + .replace('{basePath}', Legacy.shims.getBasePath()) .replace('{elasticWebsiteUrl}', Legacy.shims.docLinks.ELASTIC_WEBSITE_URL) .replace('{docLinkVersion}', Legacy.shims.docLinks.DOC_LINK_VERSION); const index = text.indexOf(linkPart[0]); diff --git a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx index 062c32c7587942..0400810a8c3790 100644 --- a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx @@ -15,7 +15,6 @@ import { ALERT_MEMORY_USAGE, ALERT_DETAILS } from '../../../common/constants'; export function createMemoryUsageAlertType(): AlertTypeModel { return { id: ALERT_MEMORY_USAGE, - name: ALERT_DETAILS[ALERT_MEMORY_USAGE].label, description: ALERT_DETAILS[ALERT_MEMORY_USAGE].description, iconClass: 'bell', documentationUrl(docLinks) { diff --git a/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx index ec97a45a8a8005..fdb89033c4e2ca 100644 --- a/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx @@ -13,7 +13,6 @@ import { Expression } from './expression'; export function createMissingMonitoringDataAlertType(): AlertTypeModel { return { id: ALERT_MISSING_MONITORING_DATA, - name: ALERT_DETAILS[ALERT_MISSING_MONITORING_DATA].label, description: ALERT_DETAILS[ALERT_MISSING_MONITORING_DATA].description, iconClass: 'bell', documentationUrl(docLinks) { diff --git a/x-pack/plugins/monitoring/public/alerts/panel.tsx b/x-pack/plugins/monitoring/public/alerts/panel.tsx index 139010a3d24469..2d319a81dd0630 100644 --- a/x-pack/plugins/monitoring/public/alerts/panel.tsx +++ b/x-pack/plugins/monitoring/public/alerts/panel.tsx @@ -10,6 +10,7 @@ import { EuiHorizontalRule, EuiListGroup, EuiListGroupItem, + EuiCodeBlock, } from '@elastic/eui'; import { CommonAlert, CommonAlertState, AlertMessage } from '../../common/types/alerts'; @@ -47,12 +48,24 @@ export const AlertPanel: React.FC = (props: Props) => { ) : null; + const { code } = alertState.state.ui.message; return (
    {replaceTokens(alertState.state.ui.message)}
    + {code?.length ? ( + + {code} + + ) : null} {nextStepsUi ? : null} {nextStepsUi}
    diff --git a/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx index bd0e7f89bf535e..403a1e531258e2 100644 --- a/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx @@ -28,7 +28,6 @@ export function createThreadPoolRejectionsAlertType( ): AlertTypeModel { return { id: alertId, - name: threadPoolAlertDetails.label, description: threadPoolAlertDetails.description, iconClass: 'bell', documentationUrl(docLinks) { diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js index ded309ce64e2ec..8849fb05fcf3c7 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js @@ -47,6 +47,7 @@ import { ALERT_NODES_CHANGED, ALERT_ELASTICSEARCH_VERSION_MISMATCH, ALERT_MISSING_MONITORING_DATA, + ALERT_CCR_READ_EXCEPTIONS, } from '../../../../common/constants'; import { AlertsBadge } from '../../../alerts/badge'; import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badge'; @@ -159,7 +160,11 @@ function renderLog(log) { ); } -const OVERVIEW_PANEL_ALERTS = [ALERT_CLUSTER_HEALTH, ALERT_LICENSE_EXPIRATION]; +const OVERVIEW_PANEL_ALERTS = [ + ALERT_CLUSTER_HEALTH, + ALERT_LICENSE_EXPIRATION, + ALERT_CCR_READ_EXCEPTIONS, +]; const NODES_PANEL_ALERTS = [ ALERT_CPU_USAGE, diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap index d54612b6f4f29a..794982a0b6193d 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap @@ -29,6 +29,12 @@ exports[`Ccr that it renders normally 1`] = ` "name": "Follows", "sortable": true, }, + Object { + "field": "alerts", + "name": "Alerts", + "render": [Function], + "sortable": true, + }, Object { "field": "syncLagOps", "name": "Sync Lag (ops)", diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js index ab26b6a9cc0bb6..8b7c386a4dcc6c 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, Component } from 'react'; +import React, { Fragment, useState } from 'react'; import { EuiInMemoryTable, EuiLink, @@ -20,27 +20,20 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; +import { AlertsStatus } from '../../../alerts/status'; import './ccr.scss'; function toSeconds(ms) { return Math.floor(ms / 1000) + 's'; } -export class Ccr extends Component { - constructor(props) { - super(props); - this.state = { - itemIdToExpandedRowMap: {}, - }; - } - - toggleShards(index, shards) { - const itemIdToExpandedRowMap = { - ...this.state.itemIdToExpandedRowMap, - }; +export const Ccr = (props) => { + const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState({}); + const toggleShards = (index, shards) => { + const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; - if (itemIdToExpandedRowMap[index]) { - delete itemIdToExpandedRowMap[index]; + if (itemIdToExpandedRowMapValues[index]) { + delete itemIdToExpandedRowMapValues[index]; } else { let pagination = { initialPageSize: 5, @@ -51,7 +44,7 @@ export class Ccr extends Component { pagination = false; } - itemIdToExpandedRowMap[index] = ( + itemIdToExpandedRowMapValues[index] = ( null, }, + { + field: 'alerts', + sortable: true, + name: i18n.translate( + 'xpack.monitoring.elasticsearch.ccr.shardsTable.alertsColumnTitle', + { + defaultMessage: 'Alerts', + } + ), + render: (_field, item) => { + return ( + state.meta.shardId === item.shardId} + /> + ); + }, + }, { field: 'syncLagOps', name: i18n.translate( @@ -157,11 +169,11 @@ export class Ccr extends Component { /> ); } - this.setState({ itemIdToExpandedRowMap }); - } + setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); + }; - renderTable() { - const { data } = this.props; + const renderTable = () => { + const { data, alerts } = props; const items = data; let pagination = { @@ -194,9 +206,9 @@ export class Ccr extends Component { ), sortable: true, render: (index, { shards }) => { - const expanded = !!this.state.itemIdToExpandedRowMap[index]; + const expanded = !!itemIdToExpandedRowMap[index]; return ( - this.toggleShards(index, shards)}> + toggleShards(index, shards)}> {index}   {expanded ? : } @@ -214,6 +226,25 @@ export class Ccr extends Component { } ), }, + { + field: 'alerts', + sortable: true, + name: i18n.translate( + 'xpack.monitoring.elasticsearch.ccr.ccrListingTable.alertsColumnTitle', + { + defaultMessage: 'Alerts', + } + ), + render: (_field, item) => { + return ( + state.meta.followerIndex === item.index} + /> + ); + }, + }, { field: 'syncLagOps', sortable: true, @@ -264,28 +295,26 @@ export class Ccr extends Component { }} sorting={sorting} itemId="id" - itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap} + itemIdToExpandedRowMap={itemIdToExpandedRowMap} /> ); - } + }; - render() { - return ( - - - -

    - -

    -
    - - {this.renderTable()} - -
    -
    - ); - } -} + return ( + + + +

    + +

    +
    + + {renderTable()} + +
    +
    + ); +}; diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap index e35d2ba6108f52..81398c1d8e8362 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap @@ -2,50 +2,46 @@ exports[`CcrShard that is renders an exception properly 1`] = ` - -

    - - - -

    -
    - -
    `; @@ -59,44 +55,50 @@ exports[`CcrShard that it renders normally 1`] = ` } > - + + + + - - + + + + + + {this.renderErrors()} {this.renderCharts()} diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js index 52de0659ed527b..657301d6e1cb3e 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js @@ -8,8 +8,9 @@ import React from 'react'; import { SummaryStatus } from '../../summary_status'; import { formatMetric } from '../../../lib/format_number'; import { i18n } from '@kbn/i18n'; +import { AlertsStatus } from '../../../alerts/status'; -export function Status({ stat, formattedLeader, oldestStat }) { +export function Status({ stat, formattedLeader, oldestStat, alerts = {} }) { const { follower_index: followerIndex, shard_id: shardId, @@ -23,6 +24,12 @@ export function Status({ stat, formattedLeader, oldestStat }) { } = oldestStat; const metrics = [ + { + label: i18n.translate('xpack.monitoring.elasticsearch.ccrShard.status.alerts', { + defaultMessage: 'Alerts', + }), + value: , + }, { label: i18n.translate('xpack.monitoring.elasticsearch.ccrShard.status.followerIndexLabel', { defaultMessage: 'Follower Index', diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts index 0439b47569e720..a0de3a7663a129 100644 --- a/x-pack/plugins/monitoring/public/plugin.ts +++ b/x-pack/plugins/monitoring/public/plugin.ts @@ -156,6 +156,7 @@ export class MonitoringPlugin './alerts/thread_pool_rejections_alert' ); const { createMemoryUsageAlertType } = await import('./alerts/memory_usage_alert'); + const { createCCRReadExceptionsAlertType } = await import('./alerts/ccr_read_exceptions_alert'); const { triggersActionsUi: { alertTypeRegistry }, @@ -176,6 +177,7 @@ export class MonitoringPlugin ALERT_DETAILS[ALERT_THREAD_POOL_WRITE_REJECTIONS] ) ); + alertTypeRegistry.register(createCCRReadExceptionsAlertType()); const legacyAlertTypes = createLegacyAlertTypes(); for (const legacyAlertType of legacyAlertTypes) { alertTypeRegistry.register(legacyAlertType); diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js index 65693407857363..9e26d453d76a35 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js @@ -12,7 +12,13 @@ import { routeInitProvider } from '../../../lib/route_init'; import template from './index.html'; import { Ccr } from '../../../components/elasticsearch/ccr'; import { MonitoringViewBaseController } from '../../base_controller'; -import { CODE_PATH_ELASTICSEARCH } from '../../../../common/constants'; +import { + CODE_PATH_ELASTICSEARCH, + ALERT_CCR_READ_EXCEPTIONS, + ELASTICSEARCH_SYSTEM_ID, +} from '../../../../common/constants'; +import { SetupModeRenderer } from '../../../components/renderers'; +import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context'; uiRoutes.when('/elasticsearch/ccr', { template, @@ -37,6 +43,12 @@ uiRoutes.when('/elasticsearch/ccr', { getPageData, $scope, $injector, + alerts: { + shouldFetch: true, + options: { + alertTypeIds: [ALERT_CCR_READ_EXCEPTIONS], + }, + }, }); $scope.$watch( @@ -45,7 +57,20 @@ uiRoutes.when('/elasticsearch/ccr', { if (!data) { return; } - this.renderReact(); + this.renderReact( + ( + + {flyoutComponent} + + {bottomBarComponent} + + )} + /> + ); } ); } diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js index 33a2d27f398565..6c1c4218568e35 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js @@ -13,7 +13,13 @@ import { routeInitProvider } from '../../../../lib/route_init'; import template from './index.html'; import { MonitoringViewBaseController } from '../../../base_controller'; import { CcrShard } from '../../../../components/elasticsearch/ccr_shard'; -import { CODE_PATH_ELASTICSEARCH } from '../../../../../common/constants'; +import { + CODE_PATH_ELASTICSEARCH, + ALERT_CCR_READ_EXCEPTIONS, + ELASTICSEARCH_SYSTEM_ID, +} from '../../../../../common/constants'; +import { SetupModeRenderer } from '../../../../components/renderers'; +import { SetupModeContext } from '../../../../components/setup_mode/setup_mode_context'; uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', { template, @@ -27,6 +33,7 @@ uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', { controllerAs: 'elasticsearchCcr', controller: class ElasticsearchCcrController extends MonitoringViewBaseController { constructor($injector, $scope, pageData) { + const $route = $injector.get('$route'); super({ title: i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.routeTitle', { defaultMessage: 'Elasticsearch - Ccr - Shard', @@ -35,6 +42,17 @@ uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', { getPageData, $scope, $injector, + alerts: { + shouldFetch: true, + options: { + alertTypeIds: [ALERT_CCR_READ_EXCEPTIONS], + filters: [ + { + shardId: $route.current.pathParams.shardId, + }, + ], + }, + }, }); $scope.instance = i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.instanceTitle', { @@ -62,7 +80,20 @@ uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', { }) ); - this.renderReact(); + this.renderReact( + ( + + {flyoutComponent} + + {bottomBarComponent} + + )} + /> + ); } ); } diff --git a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts index b43a56562a2aab..64b7148d87d9e4 100644 --- a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts +++ b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts @@ -5,6 +5,7 @@ */ import { + CCRReadExceptionsAlert, CpuUsageAlert, MissingMonitoringDataAlert, DiskUsageAlert, @@ -32,6 +33,7 @@ import { ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_KIBANA_VERSION_MISMATCH, ALERT_ELASTICSEARCH_VERSION_MISMATCH, + ALERT_CCR_READ_EXCEPTIONS, } from '../../common/constants'; import { AlertsClient } from '../../../alerts/server'; import { Alert } from '../../../alerts/common'; @@ -49,6 +51,7 @@ const BY_TYPE = { [ALERT_LOGSTASH_VERSION_MISMATCH]: LogstashVersionMismatchAlert, [ALERT_KIBANA_VERSION_MISMATCH]: KibanaVersionMismatchAlert, [ALERT_ELASTICSEARCH_VERSION_MISMATCH]: ElasticsearchVersionMismatchAlert, + [ALERT_CCR_READ_EXCEPTIONS]: CCRReadExceptionsAlert, }; export class AlertsFactory { @@ -68,7 +71,6 @@ export class AlertsFactory { if (!alertClientAlerts.total || !alertClientAlerts.data?.length) { return; - // return new alertCls() as BaseAlert; } const [rawAlert] = alertClientAlerts.data as [Alert]; diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index ebff72a255777f..a3bcc310b80847 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -345,7 +345,7 @@ export class BaseAlert { const firingNodeUuids = nodes .filter((node) => node.shouldFire) - .map((node) => node.meta.nodeId) + .map((node) => node.meta.nodeId || node.meta.instanceId) .join(','); const instanceId = `${this.alertOptions.id}:${cluster.clusterUuid}:${firingNodeUuids}`; const instance = services.alertInstanceFactory(instanceId); @@ -355,13 +355,16 @@ export class BaseAlert { if (!node.shouldFire) { continue; } - const stat = node.meta as AlertNodeState; + const { meta } = node; const nodeState = this.getDefaultAlertState(cluster, node) as AlertNodeState; if (key) { - nodeState[key] = stat[key]; + nodeState[key] = meta[key]; } - nodeState.nodeId = stat.nodeId || node.nodeId!; - nodeState.nodeName = stat.nodeName || node.nodeName || nodeState.nodeId; + nodeState.nodeId = meta.nodeId || node.nodeId! || meta.instanceId; + // TODO: make these functions more generic, so it's node/item agnostic + nodeState.nodeName = meta.itemLabel || meta.nodeName || node.nodeName || nodeState.nodeId; + nodeState.itemLabel = meta.itemLabel; + nodeState.meta = meta; nodeState.ui.triggeredMS = currentUTC; nodeState.ui.isFiring = true; nodeState.ui.severity = node.severity; diff --git a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts new file mode 100644 index 00000000000000..6034f32a8c6598 --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts @@ -0,0 +1,289 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { BaseAlert } from './base_alert'; +import { + AlertData, + AlertCluster, + AlertState, + AlertMessage, + CCRReadExceptionsUIMeta, + AlertMessageTimeToken, + AlertMessageLinkToken, + AlertInstanceState, + CommonAlertParams, + CommonAlertFilter, + CCRReadExceptionsStats, +} from '../../common/types/alerts'; +import { AlertInstance } from '../../../alerts/server'; +import { + INDEX_PATTERN_ELASTICSEARCH, + ALERT_CCR_READ_EXCEPTIONS, + ALERT_DETAILS, +} from '../../common/constants'; +import { fetchCCRReadExceptions } from '../lib/alerts/fetch_ccr_read_exceptions'; +import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; +import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; +import { parseDuration } from '../../../alerts/common/parse_duration'; +import { SanitizedAlert, RawAlertInstance } from '../../../alerts/common'; +import { AlertingDefaults, createLink } from './alert_helpers'; +import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index'; +import { Globals } from '../static_globals'; + +export class CCRReadExceptionsAlert extends BaseAlert { + constructor(public rawAlert?: SanitizedAlert) { + super(rawAlert, { + id: ALERT_CCR_READ_EXCEPTIONS, + name: ALERT_DETAILS[ALERT_CCR_READ_EXCEPTIONS].label, + throttle: '6h', + defaultParams: { + duration: '1h', + }, + actionVariables: [ + { + name: 'remoteClusters', + description: i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.actionVariables.remoteClusters', + { + defaultMessage: 'List of remote clusters that are experiencing CCR read exceptions.', + } + ), + }, + { + name: 'followerIndices', + description: i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.actionVariables.followerIndices', + { + defaultMessage: 'List of follower indices reporting CCR read exceptions.', + } + ), + }, + ...Object.values(AlertingDefaults.ALERT_TYPE.context), + ], + }); + } + + protected async fetchData( + params: CommonAlertParams, + callCluster: any, + clusters: AlertCluster[], + availableCcs: string[] + ): Promise { + let esIndexPattern = appendMetricbeatIndex(Globals.app.config, INDEX_PATTERN_ELASTICSEARCH); + if (availableCcs) { + esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); + } + const { duration: durationString } = params; + const duration = parseDuration(durationString); + const endMs = +new Date(); + const startMs = endMs - duration; + const stats = await fetchCCRReadExceptions( + callCluster, + esIndexPattern, + startMs, + endMs, + Globals.app.config.ui.max_bucket_size + ); + + return stats.map((stat) => { + const { + remoteCluster, + followerIndex, + shardId, + leaderIndex, + lastReadException, + clusterUuid, + ccs, + } = stat; + return { + shouldFire: true, + severity: AlertSeverity.Danger, + meta: { + remoteCluster, + followerIndex, + shardId, + leaderIndex, + lastReadException, + instanceId: `${remoteCluster}:${followerIndex}`, + itemLabel: followerIndex, + }, + clusterUuid, + ccs, + }; + }); + } + + protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage { + const { + remoteCluster, + followerIndex, + shardId, + lastReadException, + } = item.meta as CCRReadExceptionsUIMeta; + return { + text: i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.ui.firingMessage', { + defaultMessage: `Follower index #start_link{followerIndex}#end_link is reporting CCR read exceptions on remote cluster: {remoteCluster} at #absolute`, + values: { + remoteCluster, + followerIndex, + }, + }), + code: JSON.stringify(lastReadException, null, 2), + nextSteps: [ + createLink( + i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.identifyCCRStats', + { + defaultMessage: '#start_linkIdentify CCR usage/stats#end_link', + } + ), + 'elasticsearch/ccr', + AlertMessageTokenType.Link + ), + createLink( + i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.stackManagmentFollow', + { + defaultMessage: '#start_linkManage CCR follower indices#end_link', + } + ), + `{basePath}management/data/cross_cluster_replication/follower_indices` + ), + createLink( + i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.stackManagmentAutoFollow', + { + defaultMessage: '#start_linkCreate auto-follow patterns#end_link', + } + ), + `{basePath}management/data/cross_cluster_replication/auto_follow_patterns` + ), + createLink( + i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.followerAPIDoc', { + defaultMessage: '#start_linkAdd follower index API (Docs)#end_link', + }), + `{elasticWebsiteUrl}guide/en/elasticsearch/reference/{docLinkVersion}/ccr-put-follow.html` + ), + createLink( + i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.ccrDocs', { + defaultMessage: '#start_linkCross-cluster replication (Docs)#end_link', + }), + `{elasticWebsiteUrl}guide/en/elasticsearch/reference/{docLinkVersion}/xpack-ccr.html` + ), + createLink( + i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.biDirectionalReplication', + { + defaultMessage: '#start_linkBi-directional replication (Blog)#end_link', + } + ), + `{elasticWebsiteUrl}blog/bi-directional-replication-with-elasticsearch-cross-cluster-replication-ccr` + ), + createLink( + i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.followTheLeader', { + defaultMessage: '#start_linkFollow the Leader (Blog)#end_link', + }), + `{elasticWebsiteUrl}blog/follow-the-leader-an-introduction-to-cross-cluster-replication-in-elasticsearch` + ), + ], + tokens: [ + { + startToken: '#absolute', + type: AlertMessageTokenType.Time, + isAbsolute: true, + isRelative: false, + timestamp: alertState.ui.triggeredMS, + } as AlertMessageTimeToken, + { + startToken: '#start_link', + endToken: '#end_link', + type: AlertMessageTokenType.Link, + url: `elasticsearch/ccr/${followerIndex}/shard/${shardId}`, + } as AlertMessageLinkToken, + ], + }; + } + + protected filterAlertInstance(alertInstance: RawAlertInstance, filters: CommonAlertFilter[]) { + const alertInstanceStates = alertInstance.state?.alertStates as AlertState[]; + const alertFilter = filters?.find((filter) => filter.shardId); + if (!filters || !filters.length || !alertInstanceStates?.length || !alertFilter?.shardId) { + return alertInstance; + } + const shardIdInt = parseInt(alertFilter.shardId!, 10); + const alertStates = alertInstanceStates.filter( + ({ meta }) => (meta as CCRReadExceptionsStats).shardId === shardIdInt + ); + return { state: { alertStates } }; + } + + protected executeActions( + instance: AlertInstance, + { alertStates }: AlertInstanceState, + item: AlertData | null, + cluster: AlertCluster + ) { + const remoteClustersList = alertStates + .map((alertState) => (alertState.meta as CCRReadExceptionsUIMeta).remoteCluster) + .join(', '); + const followerIndicesList = alertStates + .map((alertState) => (alertState.meta as CCRReadExceptionsUIMeta).followerIndex) + .join(', '); + + const shortActionText = i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.shortAction', + { + defaultMessage: + 'Verify follower and leader index relationships across the affected remote clusters.', + } + ); + const fullActionText = i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.fullAction', { + defaultMessage: 'View CCR stats', + }); + + const ccs = alertStates.find((state) => state.ccs)?.ccs; + const globalStateLink = this.createGlobalStateLink( + 'elasticsearch/ccr', + cluster.clusterUuid, + ccs + ); + + const action = `[${fullActionText}](${globalStateLink})`; + const internalShortMessage = i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.firing.internalShortMessage', + { + defaultMessage: `CCR read exceptions alert is firing for the following remote clusters: {remoteClustersList}. {shortActionText}`, + values: { + remoteClustersList, + shortActionText, + }, + } + ); + const internalFullMessage = i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.firing.internalFullMessage', + { + defaultMessage: `CCR read exceptions alert is firing for the following remote clusters: {remoteClustersList}. Current 'follower_index' indices are affected: {followerIndicesList}. {action}`, + values: { + action, + remoteClustersList, + followerIndicesList, + }, + } + ); + + instance.scheduleActions('default', { + internalShortMessage, + internalFullMessage, + state: AlertingDefaults.ALERT_STATE.firing, + remoteClusters: remoteClustersList, + followerIndices: followerIndicesList, + clusterName: cluster.clusterName, + action, + actionPlain: shortActionText, + }); + } +} diff --git a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.test.ts index 63195621fb9c84..4622f73b9feb0c 100644 --- a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.test.ts @@ -125,6 +125,13 @@ describe('CpuUsageAlert', () => { ccs: undefined, cluster: { clusterUuid, clusterName }, cpuUsage, + itemLabel: undefined, + meta: { + clusterUuid, + cpuUsage, + nodeId, + nodeName, + }, nodeId, nodeName, ui: { diff --git a/x-pack/plugins/monitoring/server/alerts/index.ts b/x-pack/plugins/monitoring/server/alerts/index.ts index 5fa718dfb34cda..b58476a01dc14f 100644 --- a/x-pack/plugins/monitoring/server/alerts/index.ts +++ b/x-pack/plugins/monitoring/server/alerts/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +export { CCRReadExceptionsAlert } from './ccr_read_exceptions_alert'; export { BaseAlert } from './base_alert'; export { CpuUsageAlert } from './cpu_usage_alert'; export { MissingMonitoringDataAlert } from './missing_monitoring_data_alert'; diff --git a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts index 6ba4333309f00a..65205738f82c36 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts @@ -131,6 +131,14 @@ describe('MissingMonitoringDataAlert', () => { nodeId, nodeName, gapDuration, + itemLabel: undefined, + meta: { + clusterUuid, + gapDuration, + limit: 86400000, + nodeId, + nodeName, + }, ui: { isFiring: true, message: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts new file mode 100644 index 00000000000000..c8933a7cd14a9c --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get } from 'lodash'; +import { CCRReadExceptionsStats } from '../../../common/types/alerts'; + +export async function fetchCCRReadExceptions( + callCluster: any, + index: string, + startMs: number, + endMs: number, + size: number +): Promise { + const params = { + index, + filterPath: ['aggregations.remote_clusters.buckets'], + body: { + size: 0, + query: { + bool: { + filter: [ + { + nested: { + path: 'ccr_stats.read_exceptions', + query: { + exists: { + field: 'ccr_stats.read_exceptions.exception', + }, + }, + }, + }, + { + term: { + type: 'ccr_stats', + }, + }, + { + range: { + timestamp: { + format: 'epoch_millis', + gte: startMs, + lte: endMs, + }, + }, + }, + ], + }, + }, + aggs: { + remote_clusters: { + terms: { + field: 'ccr_stats.remote_cluster', + size, + }, + aggs: { + follower_indices: { + terms: { + field: 'ccr_stats.follower_index', + size, + }, + aggs: { + hits: { + top_hits: { + sort: [ + { + timestamp: { + order: 'desc', + unmapped_type: 'long', + }, + }, + ], + _source: { + includes: [ + 'cluster_uuid', + 'ccr_stats.read_exceptions', + 'ccr_stats.shard_id', + 'ccr_stats.leader_index', + ], + }, + size: 1, + }, + }, + }, + }, + }, + }, + }, + }, + }; + + const response = await callCluster('search', params); + const stats: CCRReadExceptionsStats[] = []; + const { buckets: remoteClusterBuckets = [] } = response.aggregations.remote_clusters; + + if (!remoteClusterBuckets.length) { + return stats; + } + + for (const remoteClusterBucket of remoteClusterBuckets) { + const followerIndicesBuckets = remoteClusterBucket.follower_indices.buckets; + const remoteCluster = remoteClusterBucket.key; + + for (const followerIndexBucket of followerIndicesBuckets) { + const followerIndex = followerIndexBucket.key; + const { + _index: monitoringIndexName, + _source: { ccr_stats: ccrStats, cluster_uuid: clusterUuid }, + } = get(followerIndexBucket, 'hits.hits.hits[0]'); + const { + read_exceptions: readExceptions, + leader_index: leaderIndex, + shard_id: shardId, + } = ccrStats; + const { exception: lastReadException } = readExceptions[readExceptions.length - 1]; + + stats.push({ + clusterUuid, + remoteCluster, + followerIndex, + shardId, + leaderIndex, + lastReadException, + ccs: monitoringIndexName.includes(':') ? monitoringIndexName.split(':')[0] : null, + }); + } + } + return stats; +} diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 9478e24c9560f1..22ea6c31dbe69d 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -59,7 +59,7 @@ const wrapError = (error: any): CustomHttpResponseOptions => { const boom = Boom.isBoom(error) ? error : Boom.boomify(error, options); return { body: boom, - headers: boom.output.headers, + headers: boom.output.headers as { [key: string]: string }, statusCode: boom.output.statusCode, }; }; diff --git a/x-pack/plugins/reporting/server/routes/generation.ts b/x-pack/plugins/reporting/server/routes/generation.ts index ba69b97bee7f1e..064cdf0b28eb98 100644 --- a/x-pack/plugins/reporting/server/routes/generation.ts +++ b/x-pack/plugins/reporting/server/routes/generation.ts @@ -68,8 +68,8 @@ export function registerJobGenerationRoutes(reporting: ReportingCore, logger: Lo /* * Error should already have been logged by the time we get here */ - function handleError(res: typeof kibanaResponseFactory, err: Error | Boom) { - if (err instanceof Boom) { + function handleError(res: typeof kibanaResponseFactory, err: Error | Boom.Boom) { + if (err instanceof Boom.Boom) { return res.customError({ statusCode: err.output.statusCode, body: err.output.payload.message, diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js index 5e9c2f62ceef8e..bb217fbeed3048 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js @@ -45,8 +45,10 @@ export class StepLogistics extends Component { hasMatchingIndices: PropTypes.bool.isRequired, indexPatternAsyncErrors: PropTypes.array, }; + state = { cronFocus: false }; showAdvancedCron = () => { + this.setState({ cronFocus: true }); const { onFieldsChange } = this.props; onFieldsChange({ @@ -55,6 +57,7 @@ export class StepLogistics extends Component { }; hideAdvancedCron = () => { + this.setState({ cronFocus: true }); const { onFieldsChange, fields } = this.props; const { simpleRollupCron } = fields; @@ -156,6 +159,7 @@ export class StepLogistics extends Component { fullWidth > onFieldsChange({ rollupCron: e.target.value })} isInvalid={Boolean(areStepErrorsVisible && errorRollupCron)} @@ -181,6 +185,7 @@ export class StepLogistics extends Component { return ( { "path": "/path", "port": undefined, "query": undefined, - "scheme": "http:", + "scheme": "http", }, } `); @@ -321,7 +321,7 @@ describe('#httpRequestEvent', () => { "path": "/original/path", "port": undefined, "query": "query=param", - "scheme": "http:", + "scheme": "http", }, } `); diff --git a/x-pack/plugins/security/server/audit/audit_events.ts b/x-pack/plugins/security/server/audit/audit_events.ts index 7f0dd39162adff..b6538af31bd608 100644 --- a/x-pack/plugins/security/server/audit/audit_events.ts +++ b/x-pack/plugins/security/server/audit/audit_events.ts @@ -28,14 +28,9 @@ export interface AuditEvent { category?: EventCategory; type?: EventType; outcome?: EventOutcome; - module?: string; - dataset?: string; }; user?: { name: string; - email?: string; - full_name?: string; - hash?: string; roles?: readonly string[]; }; kibana?: { @@ -87,17 +82,10 @@ export interface AuditEvent { http?: { request?: { method?: string; - body?: { - content: string; - }; - }; - response?: { - status_code?: number; }; }; url?: { domain?: string; - full?: string; path?: string; port?: number; query?: string; @@ -108,14 +96,10 @@ export interface AuditEvent { export enum EventCategory { DATABASE = 'database', WEB = 'web', - IAM = 'iam', AUTHENTICATION = 'authentication', - PROCESS = 'process', } export enum EventType { - USER = 'user', - GROUP = 'group', CREATION = 'creation', ACCESS = 'access', CHANGE = 'change', @@ -152,7 +136,7 @@ export function httpRequestEvent({ request }: HttpRequestParams): AuditEvent { path: url.pathname, port: url.port ? parseInt(url.port, 10) : undefined, query: url.search ? url.search.slice(1) : undefined, - scheme: url.protocol, + scheme: url.protocol ? url.protocol.substr(0, url.protocol.length - 1) : undefined, }, }; } diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.ts index d81702691a3a12..244cf1d0a8f51f 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.ts @@ -243,7 +243,7 @@ describe('AuthenticationService', () => { it('includes `WWW-Authenticate` header if `authenticate` fails to authenticate user and provides challenges', async () => { const mockResponse = httpServerMock.createLifecycleResponseFactory(); const originalError = Boom.unauthorized('some message'); - originalError.output.headers['WWW-Authenticate'] = [ + (originalError.output.headers as { [key: string]: string })['WWW-Authenticate'] = [ 'Basic realm="Access to prod", charset="UTF-8"', 'Basic', 'Negotiate', diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index 9bf419c7dacaae..b7abed979164e3 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -333,7 +333,9 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { * @param error Error to extract challenges from. */ private getNegotiateChallenge(error: LegacyElasticsearchError) { - const challenges = ([] as string[]).concat(error.output.headers[WWWAuthenticateHeaderName]); + const challenges = ([] as string[]).concat( + (error.output.headers as { [key: string]: string })[WWWAuthenticateHeaderName] + ); const negotiateChallenge = challenges.find((challenge) => challenge.toLowerCase().startsWith('negotiate') diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts index 22037c021701f7..14941b019421b8 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts @@ -72,4 +72,4 @@ export const factory = (): PolicyConfig => { /** * Reflects what string the Endpoint will use when message field is default/empty */ -export const DefaultMalwareMessage = 'Elastic Security { action } { filename }'; +export const DefaultMalwareMessage = 'Elastic Security {action} {filename}'; diff --git a/x-pack/plugins/security_solution/common/license/license.ts b/x-pack/plugins/security_solution/common/license/license.ts index 2d424ab9c960ad..9c093016f4a38c 100644 --- a/x-pack/plugins/security_solution/common/license/license.ts +++ b/x-pack/plugins/security_solution/common/license/license.ts @@ -51,5 +51,5 @@ export class LicenseService { } export const isAtLeast = (license: ILicense | null, level: LicenseType): boolean => { - return license !== null && license.isAvailable && license.isActive && license.hasAtLeast(level); + return !!license && license.isAvailable && license.isActive && license.hasAtLeast(level); }; diff --git a/x-pack/plugins/security_solution/public/cases/containers/api.ts b/x-pack/plugins/security_solution/public/cases/containers/api.ts index ef1e35b8ceb4b2..07f7391ca94d90 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/api.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/api.ts @@ -16,7 +16,6 @@ import { CaseUserActionsResponse, CommentRequest, CommentType, - ConnectorField, ServiceConnectorCaseParams, ServiceConnectorCaseResponse, User, @@ -24,7 +23,6 @@ import { import { ACTION_TYPES_URL, - CASE_CONFIGURE_CONNECTORS_URL, CASE_REPORTERS_URL, CASE_STATUS_URL, CASE_TAGS_URL, @@ -273,20 +271,3 @@ export const getActionLicense = async (signal: AbortSignal): Promise => { - const response = await KibanaServices.get().http.fetch( - `${CASE_CONFIGURE_CONNECTORS_URL}/${connectorId}`, - { - query: { - connector_type: connectorType, - }, - method: 'GET', - signal, - } - ); - return response; -}; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_fields.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_fields.tsx deleted file mode 100644 index 6b594fa60e0c73..00000000000000 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_fields.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useCallback, useEffect, useState } from 'react'; - -import { errorToToaster, useStateToaster } from '../../common/components/toasters'; -import { getFields } from './api'; -import * as i18n from './translations'; -import { ConnectorField } from '../../../../case/common/api'; - -interface FieldsState { - fields: ConnectorField[]; - isLoading: boolean; - isError: boolean; -} - -const initialData: FieldsState = { - fields: [], - isLoading: false, - isError: false, -}; - -export interface UseGetFields extends FieldsState { - fetchFields: () => void; -} - -export const useGetFields = (connectorId: string, connectorType: string): UseGetFields => { - const [fieldsState, setFieldsState] = useState(initialData); - const [, dispatchToaster] = useStateToaster(); - - const fetchFields = useCallback(() => { - let didCancel = false; - const abortCtrl = new AbortController(); - const fetchData = async () => { - setFieldsState({ - ...fieldsState, - isLoading: true, - }); - try { - const response = await getFields(connectorId, connectorType, abortCtrl.signal); - if (!didCancel) { - setFieldsState({ - fields: response, - isLoading: false, - isError: false, - }); - } - } catch (error) { - if (!didCancel) { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); - setFieldsState({ - fields: [], - isLoading: false, - isError: true, - }); - } - } - }; - fetchData(); - return () => { - didCancel = true; - abortCtrl.abort(); - }; - }, [connectorId, connectorType, dispatchToaster, fieldsState]); - - useEffect(() => { - fetchFields(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return { - ...fieldsState, - fetchFields, - }; -}; diff --git a/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.test.ts b/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.test.ts deleted file mode 100644 index c099a1413a88f7..00000000000000 --- a/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ariaIndexToArrayIndex, arrayIndexToAriaIndex } from './helpers'; - -describe('helpers', () => { - describe('ariaIndexToArrayIndex', () => { - it('returns the expected array index', () => { - expect(ariaIndexToArrayIndex(1)).toEqual(0); - }); - }); - - describe('arrayIndexToAriaIndex', () => { - it('returns the expected aria index', () => { - expect(arrayIndexToAriaIndex(0)).toEqual(1); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.test.tsx new file mode 100644 index 00000000000000..48db4b1f261b69 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.test.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount } from 'enzyme'; +import React from 'react'; + +import { + ariaIndexToArrayIndex, + arrayIndexToAriaIndex, + getNotesContainerClassName, + getRowRendererClassName, + isArrowRight, +} from './helpers'; + +describe('helpers', () => { + describe('ariaIndexToArrayIndex', () => { + test('it returns the expected array index', () => { + expect(ariaIndexToArrayIndex(1)).toEqual(0); + }); + }); + + describe('arrayIndexToAriaIndex', () => { + test('it returns the expected aria index', () => { + expect(arrayIndexToAriaIndex(0)).toEqual(1); + }); + }); + + describe('isArrowRight', () => { + test('it returns true if the right arrow key was pressed', () => { + let result = false; + const onKeyDown = (keyboardEvent: React.KeyboardEvent) => { + result = isArrowRight(keyboardEvent); + }; + + const wrapper = mount(
    ); + wrapper.find('div').simulate('keydown', { key: 'ArrowRight' }); + wrapper.update(); + + expect(result).toBe(true); + }); + + test('it returns false if another key was pressed', () => { + let result = false; + const onKeyDown = (keyboardEvent: React.KeyboardEvent) => { + result = isArrowRight(keyboardEvent); + }; + + const wrapper = mount(
    ); + wrapper.find('div').simulate('keydown', { key: 'Enter' }); + wrapper.update(); + + expect(result).toBe(false); + }); + }); + + describe('getRowRendererClassName', () => { + test('it returns the expected class name', () => { + expect(getRowRendererClassName(2)).toBe('row-renderer-2'); + }); + }); + + describe('getNotesContainerClassName', () => { + test('it returns the expected class name', () => { + expect(getNotesContainerClassName(2)).toBe('notes-container-2'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.ts b/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.ts index d8603c9d02fcb7..8fc535c680b26f 100644 --- a/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.ts @@ -5,6 +5,11 @@ */ import { DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME } from '../drag_and_drop/helpers'; +import { + NOTES_CONTAINER_CLASS_NAME, + NOTE_CONTENT_CLASS_NAME, + ROW_RENDERER_CLASS_NAME, +} from '../../../timelines/components/timeline/body/helpers'; import { HOVER_ACTIONS_ALWAYS_SHOW_CLASS_NAME } from '../with_hover_actions'; /** @@ -63,6 +68,9 @@ export const isArrowDownOrArrowUp = (event: React.KeyboardEvent): boolean => export const isArrowKey = (event: React.KeyboardEvent): boolean => isArrowRightOrArrowLeft(event) || isArrowDownOrArrowUp(event); +/** Returns `true` if the right arrow key was pressed */ +export const isArrowRight = (event: React.KeyboardEvent): boolean => event.key === 'ArrowRight'; + /** Returns `true` if the escape key was pressed */ export const isEscape = (event: React.KeyboardEvent): boolean => event.key === 'Escape'; @@ -284,6 +292,12 @@ export type OnColumnFocused = ({ newFocusedColumnAriaColindex: number | null; }) => void; +export const getRowRendererClassName = (ariaRowindex: number) => + `${ROW_RENDERER_CLASS_NAME}-${ariaRowindex}`; + +export const getNotesContainerClassName = (ariaRowindex: number) => + `${NOTES_CONTAINER_CLASS_NAME}-${ariaRowindex}`; + /** * This function implements arrow key support for the `onKeyDownFocusHandler`. * @@ -312,6 +326,28 @@ export const onArrowKeyDown = ({ onColumnFocused?: OnColumnFocused; rowindexAttribute: string; }) => { + if (isArrowDown(event) && event.shiftKey) { + const firstRowRendererDraggable = containerElement?.querySelector( + `.${getRowRendererClassName(focusedAriaRowindex)} .${DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME}` + ); + + if (firstRowRendererDraggable) { + firstRowRendererDraggable.focus(); + return; + } + } + + if (isArrowRight(event) && event.shiftKey) { + const firstNoteContent = containerElement?.querySelector( + `.${getNotesContainerClassName(focusedAriaRowindex)} .${NOTE_CONTENT_CLASS_NAME}` + ); + + if (firstNoteContent) { + firstNoteContent.focus(); + return; + } + } + const ariaColindex = isArrowRightOrArrowLeft(event) ? getNewAriaColindex({ focusedAriaColindex, diff --git a/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.test.tsx new file mode 100644 index 00000000000000..773fc3eeff4836 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.test.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount } from 'enzyme'; +import React from 'react'; + +import { TooltipWithKeyboardShortcut } from '.'; + +const props = { + content:
    {'To pay respect'}
    , + shortcut: 'F', + showShortcut: true, +}; + +describe('TooltipWithKeyboardShortcut', () => { + test('it renders the provided content', () => { + const wrapper = mount(); + + expect(wrapper.find('[data-test-subj="content"]').text()).toBe('To pay respect'); + }); + + test('it renders the additionalScreenReaderOnlyContext', () => { + const wrapper = mount( + + ); + + expect(wrapper.find('[data-test-subj="additionalScreenReaderOnlyContext"]').text()).toBe( + 'field.name' + ); + }); + + test('it renders the expected shortcut', () => { + const wrapper = mount(); + + expect(wrapper.find('[data-test-subj="shortcut"]').first().text()).toBe('Press\u00a0F'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.tsx b/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.tsx index 807953c51a42c3..ab6f90c8fec816 100644 --- a/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiScreenReaderOnly, EuiText } from '@elastic/eui'; +import { EuiText, EuiScreenReaderOnly } from '@elastic/eui'; import React from 'react'; import * as i18n from './translations'; @@ -23,14 +23,14 @@ const TooltipWithKeyboardShortcutComponent = ({ showShortcut, }: Props) => ( <> -
    {content}
    +
    {content}
    {additionalScreenReaderOnlyContext !== '' && ( - +

    {additionalScreenReaderOnlyContext}

    )} {showShortcut && ( - + {i18n.PRESS} {'\u00a0'} {shortcut} diff --git a/x-pack/plugins/security_solution/public/common/components/current_license/index.tsx b/x-pack/plugins/security_solution/public/common/components/current_license/index.tsx new file mode 100644 index 00000000000000..27d34f5cf418f9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/current_license/index.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { FC, memo, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { Dispatch } from 'redux'; +import { licenseService } from '../../hooks/use_license'; +import { AppAction } from '../../store/actions'; +import { ILicense } from '../../../../../licensing/common/types'; + +export const CurrentLicense: FC = memo(({ children }) => { + const dispatch = useDispatch>(); + useEffect(() => { + const subscription = licenseService + .getLicenseInformation$() + ?.subscribe((licenseInformation: ILicense) => { + dispatch({ + type: 'licenseChanged', + payload: licenseInformation, + }); + }); + return () => subscription?.unsubscribe(); + }, [dispatch]); + return <>{children}; +}); + +CurrentLicense.displayName = 'CurrentLicense'; diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx index 1cf03225cec033..9ce5778fb72e5c 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx @@ -14,7 +14,6 @@ import '../../mock/match_media'; import { useKibana } from '../../lib/kibana'; import { TestProviders } from '../../mock'; import { FilterManager } from '../../../../../../../src/plugins/data/public'; -import { useAddToTimeline } from '../../hooks/use_add_to_timeline'; import { useSourcererScope } from '../../containers/sourcerer'; import { DraggableWrapperHoverContent } from './draggable_wrapper_hover_content'; import { @@ -41,8 +40,14 @@ jest.mock('uuid', () => { v4: jest.fn(() => 'uuid.v4()'), }; }); - -jest.mock('../../hooks/use_add_to_timeline'); +const mockStartDragToTimeline = jest.fn(); +jest.mock('../../hooks/use_add_to_timeline', () => { + const original = jest.requireActual('../../hooks/use_add_to_timeline'); + return { + ...original, + useAddToTimeline: () => ({ startDragToTimeline: mockStartDragToTimeline }), + }; +}); const mockAddFilters = jest.fn(); const mockGetTimelineFilterManager = jest.fn().mockReturnValue({ addFilters: mockAddFilters, @@ -78,8 +83,7 @@ const defaultProps = { describe('DraggableWrapperHoverContent', () => { beforeAll(() => { - // our mock implementation of the useAddToTimeline hook returns a mock startDragToTimeline function: - (useAddToTimeline as jest.Mock).mockReturnValue({ startDragToTimeline: jest.fn() }); + mockStartDragToTimeline.mockReset(); (useSourcererScope as jest.Mock).mockReturnValue({ browserFields: mockBrowserFields, selectedPatterns: [], @@ -376,7 +380,7 @@ describe('DraggableWrapperHoverContent', () => { }); }); - test('when clicked, it invokes the `startDragToTimeline` function returned by the `useAddToTimeline` hook', () => { + test('when clicked, it invokes the `startDragToTimeline` function returned by the `useAddToTimeline` hook', async () => { const wrapper = mount( { ); - // The following "startDragToTimeline" function returned by our mock - // useAddToTimeline hook is called when the user clicks the - // Add to timeline investigation action: - const { startDragToTimeline } = useAddToTimeline({ - draggableId, - fieldName: aggregatableStringField, - }); - wrapper.find('[data-test-subj="add-to-timeline"]').first().simulate('click'); - wrapper.update(); - waitFor(() => { - expect(startDragToTimeline).toHaveBeenCalled(); + await waitFor(() => { + wrapper.update(); + expect(mockStartDragToTimeline).toHaveBeenCalled(); }); }); }); describe('Top N', () => { - test(`it renders the 'Show top field' button when showTopN is false and an aggregatable string field is provided`, async () => { + test(`it renders the 'Show top field' button when showTopN is false and an aggregatable string field is provided`, () => { const aggregatableStringField = 'cloud.account.id'; const wrapper = mount( @@ -425,7 +421,7 @@ describe('DraggableWrapperHoverContent', () => { expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(true); }); - test(`it renders the 'Show top field' button when showTopN is false and a allowlisted signal field is provided`, async () => { + test(`it renders the 'Show top field' button when showTopN is false and a allowlisted signal field is provided`, () => { const allowlistedField = 'signal.rule.name'; const wrapper = mount( @@ -443,7 +439,7 @@ describe('DraggableWrapperHoverContent', () => { expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(true); }); - test(`it does NOT render the 'Show top field' button when showTopN is false and a field not known to BrowserFields is provided`, async () => { + test(`it does NOT render the 'Show top field' button when showTopN is false and a field not known to BrowserFields is provided`, () => { const notKnownToBrowserFields = 'unknown.field'; const wrapper = mount( @@ -461,7 +457,7 @@ describe('DraggableWrapperHoverContent', () => { expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(false); }); - test(`it should invokes goGetTimelineId when user is over the 'Show top field' button`, () => { + test(`it should invokes goGetTimelineId when user is over the 'Show top field' button`, async () => { const allowlistedField = 'signal.rule.name'; const wrapper = mount( @@ -476,12 +472,12 @@ describe('DraggableWrapperHoverContent', () => { ); const button = wrapper.find(`[data-test-subj="show-top-field"]`).first(); button.simulate('mouseenter'); - waitFor(() => { + await waitFor(() => { expect(goGetTimelineId).toHaveBeenCalledWith(true); }); }); - test(`invokes the toggleTopN function when the 'Show top field' button is clicked`, async () => { + test(`invokes the toggleTopN function when the 'Show top field' button is clicked`, () => { const allowlistedField = 'signal.rule.name'; const wrapper = mount( @@ -502,7 +498,7 @@ describe('DraggableWrapperHoverContent', () => { expect(toggleTopN).toBeCalled(); }); - test(`it does NOT render the Top N histogram when when showTopN is false`, async () => { + test(`it does NOT render the Top N histogram when when showTopN is false`, () => { const allowlistedField = 'signal.rule.name'; const wrapper = mount( @@ -522,7 +518,7 @@ describe('DraggableWrapperHoverContent', () => { ); }); - test(`it does NOT render the 'Show top field' button when showTopN is true`, async () => { + test(`it does NOT render the 'Show top field' button when showTopN is true`, () => { const allowlistedField = 'signal.rule.name'; const wrapper = mount( @@ -541,7 +537,7 @@ describe('DraggableWrapperHoverContent', () => { expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(false); }); - test(`it renders the Top N histogram when when showTopN is true`, async () => { + test(`it renders the Top N histogram when when showTopN is true`, () => { const allowlistedField = 'signal.rule.name'; const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx index 2d3fdb9cb94291..adbb38f20c0280 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx @@ -324,6 +324,7 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ color="text" data-test-subj="add-to-timeline" iconType="timeline" + onClick={handleStartDragToTimeline} /> )} diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index 515758965d6d17..7d38e3b732fc09 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -16,7 +16,11 @@ import { BrowserFields, DocValueFields } from '../../containers/source'; import { useTimelineEvents } from '../../../timelines/containers'; import { timelineActions } from '../../../timelines/store/timeline'; import { useKibana } from '../../lib/kibana'; -import { ColumnHeaderOptions, KqlMode } from '../../../timelines/store/timeline/model'; +import { + ColumnHeaderOptions, + KqlMode, + TimelineTabs, +} from '../../../timelines/store/timeline/model'; import { HeaderSection } from '../header_section'; import { defaultHeaders } from '../../../timelines/components/timeline/body/column_headers/default_headers'; import { Sort } from '../../../timelines/components/timeline/body/sort'; @@ -334,6 +338,7 @@ const EventsViewerComponent: React.FC = ({ onRuleChange={onRuleChange} refetch={refetch} sort={sort} + tabType={TimelineTabs.query} totalPages={calculateTotalPages({ itemsCount: totalCountMinusDeleted, itemsPerPage, diff --git a/x-pack/plugins/security_solution/public/common/components/page/index.tsx b/x-pack/plugins/security_solution/public/common/components/page/index.tsx index 37cc8f4ac3b93a..30a7685a193b2e 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/index.tsx @@ -89,8 +89,14 @@ export const AppGlobalStyle = createGlobalStyle<{ theme: { eui: { euiColorPrimar to zero. */ .euiScreenReaderOnly { - height: 0px; - width: 0px; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; } `; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index f06d4bdef74cb7..1fe1b809d4f30a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -52,7 +52,7 @@ import { } from '../../../../../common/detection_engine/utils'; import { EqlQueryBar } from '../eql_query_bar'; import { ThreatMatchInput } from '../threatmatch_input'; -import { useFetchIndex } from '../../../../common/containers/source'; +import { BrowserField, BrowserFields, useFetchIndex } from '../../../../common/containers/source'; import { PreviewQuery, Threshold } from '../query_preview'; const CommonUseField = getUseField({ component: Field }); @@ -168,6 +168,26 @@ const StepDefineRuleComponent: FC = ({ const queryBarQuery = formQuery != null ? formQuery.query.query : '' || initialState.queryBar.query.query; const [indexPatternsLoading, { browserFields, indexPatterns }] = useFetchIndex(index); + const aggregatableFields = Object.entries(browserFields).reduce( + (groupAcc, [groupName, groupValue]) => { + return { + ...groupAcc, + [groupName]: { + fields: Object.entries(groupValue.fields ?? {}).reduce>( + (fieldAcc, [fieldName, fieldValue]) => { + if (fieldValue.aggregatable === true) { + return { ...fieldAcc, [fieldName]: fieldValue }; + } + return fieldAcc; + }, + {} + ), + } as Partial, + }; + }, + {} + ); + const [ threatIndexPatternsLoading, { browserFields: threatBrowserFields, indexPatterns: threatIndexPatterns }, @@ -262,12 +282,12 @@ const StepDefineRuleComponent: FC = ({ const ThresholdInputChildren = useCallback( ({ thresholdField, thresholdValue }) => ( ), - [browserFields] + [aggregatableFields] ); const ThreatMatchInputChildren = useCallback( diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts index bda408cd00e75e..573442de807ae9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ILicense } from '../../../../../../../licensing/common/types'; import { GetAgentStatusResponse } from '../../../../../../../fleet/common/types/rest_spec'; import { PolicyData, UIPolicyConfig } from '../../../../../../common/endpoint/types'; import { ServerApiError } from '../../../../../common/types'; @@ -62,6 +63,11 @@ interface UserClickedPolicyDetailsSaveButton { type: 'userClickedPolicyDetailsSaveButton'; } +interface LicenseChanged { + type: 'licenseChanged'; + payload: ILicense; +} + export type PolicyDetailsAction = | ServerReturnedPolicyDetailsData | UserClickedPolicyDetailsSaveButton @@ -70,4 +76,5 @@ export type PolicyDetailsAction = | ServerReturnedUpdatedPolicyDetailsData | ServerFailedToReturnPolicyDetailsData | UserChangedPolicyConfig - | UserChangedAntivirusRegistration; + | UserChangedAntivirusRegistration + | LicenseChanged; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts index 69c2afbd019607..70ffc1f8a9fc40 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts @@ -20,6 +20,7 @@ import { } from '../../../../../common/mock/endpoint'; import { HttpFetchOptions } from 'kibana/public'; import { cloneDeep } from 'lodash'; +import { licenseMock } from '../../../../../../../licensing/common/licensing.mock'; describe('policy details: ', () => { let store: Store; @@ -151,6 +152,49 @@ describe('policy details: ', () => { expect(config!.linux.events.file).toEqual(true); }); }); + + describe('when the policy config has paid features enabled', () => { + const CustomMessage = 'Some Popup message change'; + const Basic = licenseMock.createLicense({ license: { type: 'basic', mode: 'basic' } }); + const Platinum = licenseMock.createLicense({ + license: { type: 'platinum', mode: 'platinum' }, + }); + + beforeEach(() => { + const config = policyConfig(getState()); + if (!config) { + throw new Error(); + } + + // have a paid-policy field existing in the store from a previous time + const newPayload1 = cloneDeep(config); + newPayload1.windows.popup.malware.message = CustomMessage; + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: newPayload1 }, + }); + }); + + it('preserves paid fields when license level allows', () => { + dispatch({ + type: 'licenseChanged', + payload: Platinum, + }); + const config = policyConfig(getState()); + + expect(config.windows.popup.malware.message).toEqual(CustomMessage); + }); + + it('reverts paid fields to default when license level does not allow', () => { + dispatch({ + type: 'licenseChanged', + payload: Basic, + }); + const config = policyConfig(getState()); + + expect(config.windows.popup.malware.message).not.toEqual(CustomMessage); + }); + }); }); describe('when saving policy data', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts index f039324b3af648..2f9f0d67237495 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts @@ -26,7 +26,6 @@ export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory { const http = coreStart.http; - return ({ getState, dispatch }) => (next) => async (action) => { next(action); const state = getState(); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer.ts index bcdc7ba2089c64..a6e94d3715ca35 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer.ts @@ -45,6 +45,7 @@ export const initialPolicyDetailsState: () => Immutable = () total: 0, other: 0, }, + license: undefined, }); export const policyDetailsReducer: ImmutableReducer = ( @@ -93,6 +94,13 @@ export const policyDetailsReducer: ImmutableReducer = { ...state, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts index 77e975a46d37bb..c52bef9a23b25b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts @@ -6,6 +6,8 @@ import { createSelector } from 'reselect'; import { matchPath } from 'react-router-dom'; +import { ILicense } from '../../../../../../../licensing/common/types'; +import { unsetPolicyFeaturesAboveLicenseLevel } from '../../../../../../common/license/policy_config'; import { PolicyDetailsState } from '../../types'; import { Immutable, @@ -20,6 +22,24 @@ import { ManagementRoutePolicyDetailsParams } from '../../../../types'; /** Returns the policy details */ export const policyDetails = (state: Immutable) => state.policyItem; +/** Returns current active license */ +export const licenseState = (state: Immutable) => state.license; + +export const licensedPolicy: ( + state: Immutable +) => Immutable | undefined = createSelector( + policyDetails, + licenseState, + (policyData, license) => { + if (policyData) { + unsetPolicyFeaturesAboveLicenseLevel( + policyData?.inputs[0]?.config.policy.value, + license as ILicense + ); + } + return policyData; + } +); /** * Given a Policy Data (package policy) object, return back a new object with only the field @@ -75,7 +95,7 @@ export const getPolicyDataForUpdate = ( */ export const policyDetailsForUpdate: ( state: Immutable -) => Immutable | undefined = createSelector(policyDetails, (policy) => { +) => Immutable | undefined = createSelector(licensedPolicy, (policy) => { if (policy) { return getPolicyDataForUpdate(policy); } @@ -111,7 +131,7 @@ const defaultFullPolicy: Immutable = policyConfigFactory(); * Note: this will return a default full policy if the `policyItem` is `undefined` */ export const fullPolicy: (s: Immutable) => PolicyConfig = createSelector( - policyDetails, + licensedPolicy, (policyData) => { return policyData?.inputs[0]?.config?.policy?.value ?? defaultFullPolicy; } diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts index 3926ad2220e35d..889bcc15d8df00 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ILicense } from '../../../../../licensing/common/types'; import { AppLocation, Immutable, @@ -66,6 +67,8 @@ export interface PolicyDetailsState { success: boolean; error?: ServerApiError; }; + /** current license */ + license?: ILicense; } /** diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/with_security_context.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/with_security_context.tsx index f65dbaf1087d8e..118ebdf56db907 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/with_security_context.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/with_security_context.tsx @@ -8,6 +8,7 @@ import React, { ComponentType, memo } from 'react'; import { CoreStart } from 'kibana/public'; import { combineReducers, createStore, compose, applyMiddleware } from 'redux'; import { Provider as ReduxStoreProvider } from 'react-redux'; +import { CurrentLicense } from '../../../../../common/components/current_license'; import { StartPlugins } from '../../../../../types'; import { managementReducer } from '../../../../store/reducer'; import { managementMiddlewareFactory } from '../../../../store/middleware'; @@ -57,7 +58,9 @@ export const withSecurityContext =

    ({ return ( - + + + ); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx index d611c4102e8f8c..8e631e497e57bd 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx @@ -55,16 +55,19 @@ const ProtectionRadio = React.memo(({ id, label }: { id: ProtectionModes; label: const radioButtonId = useMemo(() => htmlIdGenerator()(), []); // currently just taking windows.malware, but both windows.malware and mac.malware should be the same value const selected = policyDetailsConfig && policyDetailsConfig.windows.malware.mode; + const isPlatinumPlus = useLicense().isPlatinumPlus(); const handleRadioChange = useCallback(() => { if (policyDetailsConfig) { const newPayload = cloneDeep(policyDetailsConfig); for (const os of OSes) { newPayload[os][protection].mode = id; - if (id === ProtectionModes.prevent) { - newPayload[os].popup[protection].enabled = true; - } else { - newPayload[os].popup[protection].enabled = false; + if (isPlatinumPlus) { + if (id === ProtectionModes.prevent) { + newPayload[os].popup[protection].enabled = true; + } else { + newPayload[os].popup[protection].enabled = false; + } } } dispatch({ @@ -72,7 +75,7 @@ const ProtectionRadio = React.memo(({ id, label }: { id: ProtectionModes; label: payload: { policyConfig: newPayload }, }); } - }, [dispatch, id, policyDetailsConfig]); + }, [dispatch, id, policyDetailsConfig, isPlatinumPlus]); /** * Passing an arbitrary id because EuiRadio @@ -158,12 +161,16 @@ export const MalwareProtections = React.memo(() => { if (event.target.checked === false) { for (const os of OSes) { newPayload[os][protection].mode = ProtectionModes.off; - newPayload[os].popup[protection].enabled = event.target.checked; + if (isPlatinumPlus) { + newPayload[os].popup[protection].enabled = event.target.checked; + } } } else { for (const os of OSes) { newPayload[os][protection].mode = ProtectionModes.prevent; - newPayload[os].popup[protection].enabled = event.target.checked; + if (isPlatinumPlus) { + newPayload[os].popup[protection].enabled = event.target.checked; + } } } dispatch({ @@ -172,7 +179,7 @@ export const MalwareProtections = React.memo(() => { }); } }, - [dispatch, policyDetailsConfig] + [dispatch, policyDetailsConfig, isPlatinumPlus] ); const handleUserNotificationCheckbox = useCallback( @@ -243,6 +250,7 @@ export const MalwareProtections = React.memo(() => { id="xpack.securitySolution.endpoint.policyDetail.malware.userNotification" onChange={handleUserNotificationCheckbox} checked={userNotificationSelected} + disabled={selected === ProtectionModes.off} label={i18n.translate( 'xpack.securitySolution.endpoint.policyDetail.malware.notifyUser', { @@ -305,6 +313,7 @@ export const MalwareProtections = React.memo(() => { ); }, [ radios, + selected, isPlatinumPlus, handleUserNotificationCheckbox, userNotificationSelected, diff --git a/x-pack/plugins/security_solution/public/management/routes.tsx b/x-pack/plugins/security_solution/public/management/routes.tsx index 209d7dd6dbcde4..bc24b9ca519800 100644 --- a/x-pack/plugins/security_solution/public/management/routes.tsx +++ b/x-pack/plugins/security_solution/public/management/routes.tsx @@ -8,13 +8,16 @@ import React from 'react'; import { Route, Switch } from 'react-router-dom'; import { ManagementContainer } from './pages'; import { NotFoundPage } from '../app/404'; +import { CurrentLicense } from '../common/components/current_license'; /** * Returns the React Router Routes for the management area */ export const ManagementRoutes = () => ( - - - } /> - + + + + } /> + + ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx index afec2055140d34..febbbb23db1efa 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx @@ -11,6 +11,7 @@ import '../../../../common/mock/formatted_relative'; import { NoteCards } from '.'; import { TimelineStatus } from '../../../../../common/types/timeline'; import { TestProviders } from '../../../../common/mock'; +import { TimelineResultNote } from '../../open_timeline/types'; const getNotesByIds = () => ({ abc: { @@ -38,35 +39,42 @@ jest.mock('../../../../common/hooks/use_selector', () => ({ })); describe('NoteCards', () => { - const noteIds = ['abc', 'def']; + const notes: TimelineResultNote[] = Object.entries(getNotesByIds()).map( + ([_, { created, id, note, saveObjectId, user }]) => ({ + saveObjectId, + note, + noteId: id, + updated: created.getTime(), + updatedBy: user, + }) + ); const props = { associateNote: jest.fn(), ariaRowindex: 2, getNotesByIds, getNewNoteId: jest.fn(), - noteIds, + notes: [], showAddNote: true, status: TimelineStatus.active, toggleShowAddNote: jest.fn(), updateNote: jest.fn(), }; - test('it renders the notes column when noteIds are specified', () => { + test('it renders the notes column when notes are specified', () => { const wrapper = mount( - + ); expect(wrapper.find('[data-test-subj="notes"]').exists()).toEqual(true); }); - test('it does NOT render the notes column when noteIds are NOT specified', () => { - const testProps = { ...props, noteIds: [] }; + test('it does NOT render the notes column when notes are NOT specified', () => { const wrapper = mount( - + ); @@ -76,7 +84,7 @@ describe('NoteCards', () => { test('renders note cards', () => { const wrapper = mount( - + ); @@ -85,6 +93,18 @@ describe('NoteCards', () => { ); }); + test('renders the expected screenreader only text', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="screenReaderOnly"]').first().text()).toEqual( + 'You are viewing notes for the event in row 2. Press the up arrow key when finished to return to the event.' + ); + }); + test('it shows controls for adding notes when showAddNote is true', () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx index 99cf8740809dae..9b307690cf12c8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx @@ -5,11 +5,10 @@ */ import { EuiFlexGroup, EuiPanel, EuiScreenReaderOnly } from '@elastic/eui'; -import React, { useState, useCallback, useMemo } from 'react'; +import React, { useState, useCallback } from 'react'; import styled from 'styled-components'; -import { appSelectors } from '../../../../common/store'; -import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { getNotesContainerClassName } from '../../../../common/components/accessibility/helpers'; import { AddNote } from '../add_note'; import { AssociateNote } from '../helpers'; import { NotePreviews, NotePreviewsContainer } from '../../open_timeline/note_previews'; @@ -44,16 +43,14 @@ NotesContainer.displayName = 'NotesContainer'; interface Props { ariaRowindex: number; associateNote: AssociateNote; - noteIds: string[]; + notes: TimelineResultNote[]; showAddNote: boolean; toggleShowAddNote: () => void; } /** A view for entering and reviewing notes */ export const NoteCards = React.memo( - ({ ariaRowindex, associateNote, noteIds, showAddNote, toggleShowAddNote }) => { - const getNotesByIds = useMemo(() => appSelectors.notesByIdsSelector(), []); - const notesById = useDeepEqualSelector(getNotesByIds); + ({ ariaRowindex, associateNote, notes, showAddNote, toggleShowAddNote }) => { const [newNote, setNewNote] = useState(''); const associateNoteAndToggleShow = useCallback( @@ -64,23 +61,16 @@ export const NoteCards = React.memo( [associateNote, toggleShowAddNote] ); - const notes: TimelineResultNote[] = useMemo( - () => - appSelectors.getNotes(notesById, noteIds).map((note) => ({ - savedObjectId: note.saveObjectId, - note: note.note, - noteId: note.id, - updated: (note.lastEdit ?? note.created).getTime(), - updatedBy: note.user, - })), - [notesById, noteIds] - ); - return ( {notes.length ? ( - +

    {i18n.YOU_ARE_VIEWING_NOTES(ariaRowindex)}

    diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx index 2a1d0d2ad11cf2..fc05e61442e83d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx @@ -5,7 +5,7 @@ */ import { uniqBy } from 'lodash/fp'; -import { EuiAvatar, EuiButtonIcon, EuiCommentList } from '@elastic/eui'; +import { EuiAvatar, EuiButtonIcon, EuiCommentList, EuiScreenReaderOnly } from '@elastic/eui'; import { FormattedRelative } from '@kbn/i18n/react'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; @@ -15,6 +15,7 @@ import { TimelineResultNote } from '../types'; import { getEmptyValue, defaultToEmptyTag } from '../../../../common/components/empty_value'; import { MarkdownRenderer } from '../../../../common/components/markdown_editor'; import { timelineActions } from '../../../store/timeline'; +import { NOTE_CONTENT_CLASS_NAME } from '../../timeline/body/helpers'; import * as i18n from './translations'; export const NotePreviewsContainer = styled.section` @@ -89,7 +90,14 @@ export const NotePreviews = React.memo( ) : ( getEmptyValue() ), - children: {note.note ?? ''}, + children: ( +
    + +

    {i18n.USER_ADDED_A_NOTE(note.updatedBy ?? i18n.AN_UNKNOWN_USER)}

    +
    + {note.note ?? ''} +
    + ), actions: eventId && timelineId ? ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/translations.ts index 9857e55e365700..d38dee8a415045 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/translations.ts @@ -12,3 +12,16 @@ export const TOGGLE_EXPAND_EVENT_DETAILS = i18n.translate( defaultMessage: 'Expand event details', } ); + +export const USER_ADDED_A_NOTE = (user: string) => + i18n.translate('xpack.securitySolution.timeline.userAddedANoteScreenReaderOnly', { + values: { user }, + defaultMessage: '{user} added a note', + }); + +export const AN_UNKNOWN_USER = i18n.translate( + 'xpack.securitySolution.timeline.anUnknownUserScreenReaderOnly', + { + defaultMessage: 'an unknown user', + } +); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap index 8f514ca49e8480..d112a665d77c00 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap @@ -44,6 +44,7 @@ exports[`Columns it renders the expected columns 1`] = ` truncate={true} /> + 0 + 0 + 0 + 0 + 0 + 0 + 0 `; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx index 00b3a10bba5386..d7931b563c7779 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx @@ -26,6 +26,8 @@ describe('Columns', () => { columnRenderers={columnRenderers} data={mockTimelineData[0].data} ecsData={mockTimelineData[0].ecs} + hasRowRenderers={false} + notesCount={0} timelineId="test" /> ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx index 6dad9851e5adba..c497d4f459f000 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx @@ -21,12 +21,14 @@ import * as i18n from './translations'; interface Props { _id: string; - activeTab?: TimelineTabs; ariaRowindex: number; columnHeaders: ColumnHeaderOptions[]; columnRenderers: ColumnRenderer[]; data: TimelineNonEcsData[]; ecsData: Ecs; + hasRowRenderers: boolean; + notesCount: number; + tabType?: TimelineTabs; timelineId: string; } @@ -74,12 +76,23 @@ export const onKeyDown = (keyboardEvent: React.KeyboardEvent) => { }; export const DataDrivenColumns = React.memo( - ({ _id, activeTab, ariaRowindex, columnHeaders, columnRenderers, data, ecsData, timelineId }) => ( + ({ + _id, + ariaRowindex, + columnHeaders, + columnRenderers, + data, + ecsData, + hasRowRenderers, + notesCount, + tabType, + timelineId, + }) => ( {columnHeaders.map((header, i) => ( ( eventId: _id, field: header, linkValues: getOr([], header.linkField ?? '', ecsData), - timelineId: activeTab != null ? `${timelineId}-${activeTab}` : timelineId, + timelineId: tabType != null ? `${timelineId}-${tabType}` : timelineId, truncate: true, values: getMappedNonEcsValue({ data, @@ -104,6 +117,17 @@ export const DataDrivenColumns = React.memo( })} + {hasRowRenderers && ( + +

    {i18n.EVENT_HAS_AN_EVENT_RENDERER(ariaRowindex)}

    +
    + )} + + {notesCount && ( + +

    {i18n.EVENT_HAS_NOTES({ row: ariaRowindex, notesCount })}

    +
    + )}
    ))}
    diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/translations.ts index 80199e0026ac32..63086d56d07530 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/translations.ts @@ -11,3 +11,17 @@ export const YOU_ARE_IN_A_TABLE_CELL = ({ column, row }: { column: number; row: values: { column, row }, defaultMessage: 'You are in a table cell. row: {row}, column: {column}', }); + +export const EVENT_HAS_AN_EVENT_RENDERER = (row: number) => + i18n.translate('xpack.securitySolution.timeline.eventHasEventRendererScreenReaderOnly', { + values: { row }, + defaultMessage: + 'The event in row {row} has an event renderer. Press shift + down arrow to focus it.', + }); + +export const EVENT_HAS_NOTES = ({ notesCount, row }: { notesCount: number; row: number }) => + i18n.translate('xpack.securitySolution.timeline.eventHasNotesScreenReaderOnly', { + values: { notesCount, row }, + defaultMessage: + 'The event in row {row} has {notesCount, plural, =1 {a note} other {{notesCount} notes}}. Press shift + right arrow to focus notes.', + }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx index 9bb8a695454d74..0525767e616bef 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx @@ -36,8 +36,10 @@ describe('EventColumnView', () => { }, eventIdToNoteIds: {}, expanded: false, + hasRowRenderers: false, loading: false, loadingEventIds: [], + notesCount: 0, onEventToggled: jest.fn(), onPinEvent: jest.fn(), onRowSelected: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx index 6aee6f9d4fdfab..ae8d2a47c7dc78 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx @@ -35,7 +35,6 @@ import * as i18n from '../translations'; interface Props { id: string; actionsColumnWidth: number; - activeTab?: TimelineTabs; ariaRowindex: number; columnHeaders: ColumnHeaderOptions[]; columnRenderers: ColumnRenderer[]; @@ -46,15 +45,18 @@ interface Props { isEventPinned: boolean; isEventViewer?: boolean; loadingEventIds: Readonly; + notesCount: number; onEventToggled: () => void; onPinEvent: OnPinEvent; onRowSelected: OnRowSelected; onUnPinEvent: OnUnPinEvent; refetch: inputsModel.Refetch; onRuleChange?: () => void; + hasRowRenderers: boolean; selectedEventIds: Readonly>; showCheckboxes: boolean; showNotes: boolean; + tabType?: TimelineTabs; timelineId: string; toggleShowNotes: () => void; } @@ -65,7 +67,6 @@ export const EventColumnView = React.memo( ({ id, actionsColumnWidth, - activeTab, ariaRowindex, columnHeaders, columnRenderers, @@ -76,15 +77,18 @@ export const EventColumnView = React.memo( isEventPinned = false, isEventViewer = false, loadingEventIds, + notesCount, onEventToggled, onPinEvent, onRowSelected, onUnPinEvent, refetch, + hasRowRenderers, onRuleChange, selectedEventIds, showCheckboxes, showNotes, + tabType, timelineId, toggleShowNotes, }) => { @@ -225,12 +229,14 @@ export const EventColumnView = React.memo( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx index bce5f1293e66b8..92ae01b185f7a1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx @@ -24,7 +24,6 @@ import { eventIsPinned } from '../helpers'; const ARIA_ROW_INDEX_OFFSET = 2; interface Props { - activeTab?: TimelineTabs; actionsColumnWidth: number; browserFields: BrowserFields; columnHeaders: ColumnHeaderOptions[]; @@ -43,11 +42,11 @@ interface Props { rowRenderers: RowRenderer[]; selectedEventIds: Readonly>; showCheckboxes: boolean; + tabType?: TimelineTabs; } const EventsComponent: React.FC = ({ actionsColumnWidth, - activeTab, browserFields, columnHeaders, columnRenderers, @@ -65,11 +64,11 @@ const EventsComponent: React.FC = ({ rowRenderers, selectedEventIds, showCheckboxes, + tabType, }) => ( {data.map((event, i) => ( = ({ eventIdToNoteIds={eventIdToNoteIds} isEventPinned={eventIsPinned({ eventId: event._id, pinnedEventIds })} isEventViewer={isEventViewer} - key={`${id}_${activeTab}_${event._id}_${event._index}`} + key={`${id}_${tabType}_${event._id}_${event._index}`} lastFocusedAriaColindex={lastFocusedAriaColindex} loadingEventIds={loadingEventIds} onRowSelected={onRowSelected} @@ -89,6 +88,7 @@ const EventsComponent: React.FC = ({ onRuleChange={onRuleChange} selectedEventIds={selectedEventIds} showCheckboxes={showCheckboxes} + tabType={tabType} timelineId={id} /> ))} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index 9802e4532b05bf..e3f5a744e8b7d7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -19,22 +19,22 @@ import { OnPinEvent, OnRowSelected } from '../../events'; import { STATEFUL_EVENT_CSS_CLASS_NAME } from '../../helpers'; import { EventsTrGroup, EventsTrSupplement, EventsTrSupplementContainer } from '../../styles'; import { ColumnRenderer } from '../renderers/column_renderer'; - import { RowRenderer } from '../renderers/row_renderer'; import { isEventBuildingBlockType, getEventType } from '../helpers'; import { NoteCards } from '../../../notes/note_cards'; import { useEventDetailsWidthContext } from '../../../../../common/components/events_viewer/event_details_width_context'; import { EventColumnView } from './event_column_view'; -import { inputsModel } from '../../../../../common/store'; +import { appSelectors, inputsModel } from '../../../../../common/store'; import { timelineActions, timelineSelectors } from '../../../../store/timeline'; import { activeTimeline } from '../../../../containers/active_timeline_context'; +import { TimelineResultNote } from '../../../open_timeline/types'; +import { getRowRenderer } from '../renderers/get_row_renderer'; import { StatefulRowRenderer } from './stateful_row_renderer'; import { NOTES_BUTTON_CLASS_NAME } from '../../properties/helpers'; import { timelineDefaults } from '../../../../store/timeline/defaults'; interface Props { actionsColumnWidth: number; - activeTab?: TimelineTabs; containerRef: React.MutableRefObject; browserFields: BrowserFields; columnHeaders: ColumnHeaderOptions[]; @@ -52,6 +52,7 @@ interface Props { rowRenderers: RowRenderer[]; selectedEventIds: Readonly>; showCheckboxes: boolean; + tabType?: TimelineTabs; timelineId: string; } @@ -66,7 +67,6 @@ EventsTrSupplementContainerWrapper.displayName = 'EventsTrSupplementContainerWra const StatefulEventComponent: React.FC = ({ actionsColumnWidth, - activeTab, browserFields, containerRef, columnHeaders, @@ -84,6 +84,7 @@ const StatefulEventComponent: React.FC = ({ ariaRowindex, selectedEventIds, showCheckboxes, + tabType, timelineId, }) => { const trGroupRef = useRef(null); @@ -93,12 +94,31 @@ const StatefulEventComponent: React.FC = ({ const expandedEvent = useDeepEqualSelector( (state) => (getTimeline(state, timelineId) ?? timelineDefaults).expandedEvent ); - + const getNotesByIds = useMemo(() => appSelectors.notesByIdsSelector(), []); + const notesById = useDeepEqualSelector(getNotesByIds); + const noteIds: string[] = eventIdToNoteIds[event._id] || emptyNotes; const isExpanded = useMemo(() => expandedEvent && expandedEvent.eventId === event._id, [ event._id, expandedEvent, ]); + const notes: TimelineResultNote[] = useMemo( + () => + appSelectors.getNotes(notesById, noteIds).map((note) => ({ + savedObjectId: note.saveObjectId, + note: note.note, + noteId: note.id, + updated: (note.lastEdit ?? note.created).getTime(), + updatedBy: note.user, + })), + [notesById, noteIds] + ); + + const hasRowRenderers: boolean = useMemo(() => getRowRenderer(event.ecs, rowRenderers) != null, [ + event.ecs, + rowRenderers, + ]); + const onToggleShowNotes = useCallback(() => { const eventId = event._id; @@ -195,7 +215,6 @@ const StatefulEventComponent: React.FC = ({ = ({ ecsData={event.ecs} eventIdToNoteIds={eventIdToNoteIds} expanded={isExpanded} + hasRowRenderers={hasRowRenderers} isEventPinned={isEventPinned} isEventViewer={isEventViewer} loadingEventIds={loadingEventIds} + notesCount={notes.length} onEventToggled={handleOnEventToggled} onPinEvent={onPinEvent} onRowSelected={onRowSelected} @@ -215,6 +236,7 @@ const StatefulEventComponent: React.FC = ({ selectedEventIds={selectedEventIds} showCheckboxes={showCheckboxes} showNotes={!!showNotes[event._id]} + tabType={tabType} timelineId={timelineId} toggleShowNotes={onToggleShowNotes} /> @@ -228,7 +250,7 @@ const StatefulEventComponent: React.FC = ({ ariaRowindex={ariaRowindex} associateNote={associateNote} data-test-subj="note-cards" - noteIds={eventIdToNoteIds[event._id] || emptyNotes} + notes={notes} showAddNote={!!showNotes[event._id]} toggleShowAddNote={onToggleShowNotes} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx index 1628824b46a08d..4000ebcfd767a8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx @@ -12,6 +12,7 @@ import { BrowserFields } from '../../../../../../common/containers/source'; import { ARIA_COLINDEX_ATTRIBUTE, ARIA_ROWINDEX_ATTRIBUTE, + getRowRendererClassName, } from '../../../../../../common/components/accessibility/helpers'; import { TimelineItem } from '../../../../../../../common/search_strategy/timeline'; import { getRowRenderer } from '../../renderers/get_row_renderer'; @@ -59,28 +60,44 @@ export const StatefulRowRenderer = ({ rowindexAttribute: ARIA_ROWINDEX_ATTRIBUTE, }); + const rowRenderer = useMemo(() => getRowRenderer(event.ecs, rowRenderers), [ + event.ecs, + rowRenderers, + ]); + const content = useMemo( - () => ( - - -

    {i18n.YOU_ARE_IN_AN_EVENT_RENDERER(ariaRowindex)}

    -
    -
    - {getRowRenderer(event.ecs, rowRenderers).renderRow({ - browserFields, - data: event.ecs, - timelineId, - })} + () => + rowRenderer && ( + // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions +
    + + + +

    {i18n.YOU_ARE_IN_AN_EVENT_RENDERER(ariaRowindex)}

    +
    +
    + {rowRenderer.renderRow({ + browserFields, + data: event.ecs, + timelineId, + })} +
    +
    +
    - - ), - [ariaRowindex, browserFields, event.ecs, focusOwnership, onKeyDown, rowRenderers, timelineId] + ), + [ + ariaRowindex, + browserFields, + event.ecs, + focusOwnership, + onFocus, + onKeyDown, + onOutsideClick, + rowRenderer, + timelineId, + ] ); - return ( - // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -
    - {content} -
    - ); + return content; }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx index 3470dba636aa8c..0295d44b646d77 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx @@ -160,3 +160,9 @@ const InvestigateInResolverActionComponent: React.FC { setSelected: (jest.fn() as unknown) as StatefulBodyProps['setSelected'], sort: mockSort, showCheckboxes: false, - activeTab: TimelineTabs.query, + tabType: TimelineTabs.query, totalPages: 1, }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index f6190b39214e90..4a33d0d3af33e9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -21,7 +21,7 @@ import { BrowserFields } from '../../../../common/containers/source'; import { TimelineItem } from '../../../../../common/search_strategy/timeline'; import { inputsModel, State } from '../../../../common/store'; import { useManageTimeline } from '../../manage_timeline'; -import { ColumnHeaderOptions, TimelineModel } from '../../../store/timeline/model'; +import { ColumnHeaderOptions, TimelineModel, TimelineTabs } from '../../../store/timeline/model'; import { timelineDefaults } from '../../../store/timeline/defaults'; import { timelineActions, timelineSelectors } from '../../../store/timeline'; import { OnRowSelected, OnSelectAll } from '../events'; @@ -43,6 +43,7 @@ interface OwnProps { isEventViewer?: boolean; sort: Sort[]; refetch: inputsModel.Refetch; + tabType: TimelineTabs; totalPages: number; onRuleChange?: () => void; } @@ -60,7 +61,6 @@ export type StatefulBodyProps = OwnProps & PropsFromRedux; export const BodyComponent = React.memo( ({ - activeTab, activePage, browserFields, columnHeaders, @@ -79,6 +79,7 @@ export const BodyComponent = React.memo( showCheckboxes, refetch, sort, + tabType, totalPages, }) => { const containerRef = useRef(null); @@ -200,7 +201,6 @@ export const BodyComponent = React.memo( ( onRuleChange={onRuleChange} selectedEventIds={selectedEventIds} showCheckboxes={showCheckboxes} + tabType={tabType} /> @@ -225,7 +226,6 @@ export const BodyComponent = React.memo( ); }, (prevProps, nextProps) => - prevProps.activeTab === nextProps.activeTab && deepEqual(prevProps.browserFields, nextProps.browserFields) && deepEqual(prevProps.columnHeaders, nextProps.columnHeaders) && deepEqual(prevProps.data, nextProps.data) && @@ -238,7 +238,8 @@ export const BodyComponent = React.memo( prevProps.id === nextProps.id && prevProps.isEventViewer === nextProps.isEventViewer && prevProps.isSelectAllChecked === nextProps.isSelectAllChecked && - prevProps.showCheckboxes === nextProps.showCheckboxes + prevProps.showCheckboxes === nextProps.showCheckboxes && + prevProps.tabType === nextProps.tabType ); BodyComponent.displayName = 'BodyComponent'; @@ -253,7 +254,6 @@ const makeMapStateToProps = () => { const mapStateToProps = (state: State, { browserFields, id }: OwnProps) => { const timeline: TimelineModel = getTimeline(state, id) ?? timelineDefaults; const { - activeTab, columns, eventIdToNoteIds, excludedRowRendererIds, @@ -265,7 +265,6 @@ const makeMapStateToProps = () => { } = timeline; return { - activeTab: id === TimelineId.active ? activeTab : undefined, columnHeaders: memoizedColumnHeaders(columns, browserFields), eventIdToNoteIds, excludedRowRendererIds, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx index b4fdc427d9db3e..f3a914ff4be29c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx @@ -48,7 +48,7 @@ describe('get_column_renderer', () => { test('renders correctly against snapshot', () => { const rowRenderer = getRowRenderer(nonSuricata, rowRenderers); - const row = rowRenderer.renderRow({ + const row = rowRenderer?.renderRow({ browserFields: mockBrowserFields, data: nonSuricata, timelineId: 'test', @@ -60,7 +60,7 @@ describe('get_column_renderer', () => { test('should render plain row data when it is a non suricata row', () => { const rowRenderer = getRowRenderer(nonSuricata, rowRenderers); - const row = rowRenderer.renderRow({ + const row = rowRenderer?.renderRow({ browserFields: mockBrowserFields, data: nonSuricata, timelineId: 'test', @@ -75,7 +75,7 @@ describe('get_column_renderer', () => { test('should render a suricata row data when it is a suricata row', () => { const rowRenderer = getRowRenderer(suricata, rowRenderers); - const row = rowRenderer.renderRow({ + const row = rowRenderer?.renderRow({ browserFields: mockBrowserFields, data: suricata, timelineId: 'test', @@ -93,7 +93,7 @@ describe('get_column_renderer', () => { test('should render a suricata row data if event.category is network_traffic', () => { suricata.event = { ...suricata.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer(suricata, rowRenderers); - const row = rowRenderer.renderRow({ + const row = rowRenderer?.renderRow({ browserFields: mockBrowserFields, data: suricata, timelineId: 'test', @@ -111,7 +111,7 @@ describe('get_column_renderer', () => { test('should render a zeek row data if event.category is network_traffic', () => { zeek.event = { ...zeek.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer(zeek, rowRenderers); - const row = rowRenderer.renderRow({ + const row = rowRenderer?.renderRow({ browserFields: mockBrowserFields, data: zeek, timelineId: 'test', @@ -129,7 +129,7 @@ describe('get_column_renderer', () => { test('should render a system row data if event.category is network_traffic', () => { system.event = { ...system.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer(system, rowRenderers); - const row = rowRenderer.renderRow({ + const row = rowRenderer?.renderRow({ browserFields: mockBrowserFields, data: system, timelineId: 'test', @@ -147,7 +147,7 @@ describe('get_column_renderer', () => { test('should render a auditd row data if event.category is network_traffic', () => { auditd.event = { ...auditd.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer(auditd, rowRenderers); - const row = rowRenderer.renderRow({ + const row = rowRenderer?.renderRow({ browserFields: mockBrowserFields, data: auditd, timelineId: 'test', diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.ts index 779d54216e26c8..1662cf4037cac3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.ts @@ -7,15 +7,5 @@ import { Ecs } from '../../../../../../common/ecs'; import { RowRenderer } from './row_renderer'; -const unhandledRowRenderer = (): never => { - throw new Error('Unhandled Row Renderer'); -}; - -export const getRowRenderer = (ecs: Ecs, rowRenderers: RowRenderer[]): RowRenderer => { - const renderer = rowRenderers.find((rowRenderer) => rowRenderer.isInstance(ecs)); - if (renderer == null) { - return unhandledRowRenderer(); - } else { - return renderer; - } -}; +export const getRowRenderer = (ecs: Ecs, rowRenderers: RowRenderer[]): RowRenderer | null => + rowRenderers.find((rowRenderer) => rowRenderer.isInstance(ecs)) ?? null; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts index 8e95fc3ad238a6..f4498b10e4c8d8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts @@ -9,7 +9,6 @@ import { ColumnRenderer } from './column_renderer'; import { emptyColumnRenderer } from './empty_column_renderer'; import { netflowRowRenderer } from './netflow/netflow_row_renderer'; import { plainColumnRenderer } from './plain_column_renderer'; -import { plainRowRenderer } from './plain_row_renderer'; import { RowRenderer } from './row_renderer'; import { suricataRowRenderer } from './suricata/suricata_row_renderer'; import { unknownColumnRenderer } from './unknown_column_renderer'; @@ -29,7 +28,6 @@ export const rowRenderers: RowRenderer[] = [ suricataRowRenderer, zeekRowRenderer, netflowRowRenderer, - plainRowRenderer, // falls-back to the plain row renderer ]; export const columnRenderers: ColumnRenderer[] = [ diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx index 45c190c42605c2..a0d2ca57f90b36 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx @@ -23,7 +23,7 @@ import { EventDetailsWidthProvider } from '../../../../common/components/events_ import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { timelineDefaults } from '../../../store/timeline/defaults'; import { useSourcererScope } from '../../../../common/containers/sourcerer'; -import { TimelineModel } from '../../../store/timeline/model'; +import { TimelineModel, TimelineTabs } from '../../../store/timeline/model'; import { EventDetails } from '../event_details'; import { ToggleExpandedEvent } from '../../../store/timeline/actions'; import { State } from '../../../../common/store'; @@ -183,6 +183,7 @@ export const PinnedTabContentComponent: React.FC = ({ id={timelineId} refetch={refetch} sort={sort} + tabType={TimelineTabs.pinned} totalPages={calculateTotalPages({ itemsCount: totalCount, itemsPerPage, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index f6d6654d7fecee..c0840d58174b32 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -330,6 +330,7 @@ export const QueryTabContentComponent: React.FC = ({ id={timelineId} refetch={refetch} sort={sort} + tabType={TimelineTabs.query} totalPages={calculateTotalPages({ itemsCount: totalCount, itemsPerPage, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx index 7f0809cf9b9d8f..c97571fbbd6f35 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx @@ -199,7 +199,7 @@ const TabsContentComponent: React.FC = ({ timelineId, graphEve disabled={!graphEventId} key={TimelineTabs.graph} > - {i18n.GRAPH_TAB} + {i18n.ANALYZER_TAB} { - const policyConfig = policy.inputs[0].config?.policy.value; + const updatePolicy: UpdatePackagePolicy = { + name: policy.name, + description: policy.description, + namespace: policy.namespace, + enabled: policy.enabled, + policy_id: policy.policy_id, + output_id: policy.output_id, + package: policy.package, + inputs: policy.inputs, + version: policy.version, + }; + const policyConfig = updatePolicy.inputs[0].config?.policy.value; if (!isEndpointPolicyValidForLicense(policyConfig, license)) { - policy.inputs[0].config!.policy.value = unsetPolicyFeaturesAboveLicenseLevel( + updatePolicy.inputs[0].config!.policy.value = unsetPolicyFeaturesAboveLicenseLevel( policyConfig, license ); try { - await this.policyService.update(this.soClient, policy.id, policy); + await this.policyService.update(this.soClient, policy.id, updatePolicy); } catch (e) { // try again for transient issues try { - await this.policyService.update(this.soClient, policy.id, policy); + await this.policyService.update(this.soClient, policy.id, updatePolicy); } catch (ee) { this.logger.warn( `Unable to remove platinum features from policy ${policy.id}: ${ee.message}` diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts index 36131c2e2844d0..32c1d8d3cdf56e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts @@ -35,7 +35,7 @@ let alertsClient: ReturnType; describe('utils', () => { describe('transformError', () => { test('returns transformed output error from boom object with a 500 and payload of internal server error', () => { - const boom = new Boom('some boom message'); + const boom = new Boom.Boom('some boom message'); const transformed = transformError(boom); expect(transformed).toEqual({ message: 'An internal server error occurred', @@ -124,7 +124,7 @@ describe('utils', () => { describe('transformBulkError', () => { test('returns transformed object if it is a boom object', () => { - const boom = new Boom('some boom message', { statusCode: 400 }); + const boom = new Boom.Boom('some boom message', { statusCode: 400 }); const transformed = transformBulkError('rule-1', boom); const expected: BulkError = { rule_id: 'rule-1', @@ -252,7 +252,7 @@ describe('utils', () => { describe('transformImportError', () => { test('returns transformed object if it is a boom object', () => { - const boom = new Boom('some boom message', { statusCode: 400 }); + const boom = new Boom.Boom('some boom message', { statusCode: 400 }); const transformed = transformImportError('rule-1', boom, { success_count: 1, success: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts index 6a75d0655cf59d..022c07defc9c19 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts @@ -4,329 +4,77 @@ * you may not use this file except in compliance with the Elastic License. */ -import { sampleDocNoSortIdNoVersion } from './__mocks__/es_results'; -import { getThresholdSignalQueryFields } from './bulk_create_threshold_signals'; +import { loggingSystemMock } from '../../../../../../../src/core/server/mocks'; +import { sampleDocNoSortId, sampleDocSearchResultsNoSortId } from './__mocks__/es_results'; +import { transformThresholdResultsToEcs } from './bulk_create_threshold_signals'; +import { calculateThresholdSignalUuid } from './utils'; -describe('getThresholdSignalQueryFields', () => { - it('should return proper fields for match_phrase filters', () => { - const mockHit = { - ...sampleDocNoSortIdNoVersion(), - _source: { - '@timestamp': '2020-11-03T02:31:47.431Z', - event: { - dataset: 'traefik.access', - module: 'traefik', - }, - traefik: { - access: { - entryPointName: 'web-secure', - }, - }, - url: { - domain: 'kibana.siem.estc.dev', - }, - }, - }; - const mockFilters = { - bool: { - must: [], - filter: [ - { - bool: { - filter: [ - { - bool: { - should: [ - { - match_phrase: { - 'event.module': 'traefik', - }, - }, - ], - minimum_should_match: 1, - }, - }, - { - bool: { - filter: [ - { - bool: { - should: [ - { - match_phrase: { - 'event.dataset': 'traefik.access', - }, - }, - ], - minimum_should_match: 1, - }, - }, - { - bool: { - should: [ - { - match_phrase: { - 'traefik.access.entryPointName': 'web-secure', - }, - }, - ], - minimum_should_match: 1, - }, - }, - ], - }, - }, - ], - }, - }, - { - match_phrase: { - 'url.domain': 'kibana.siem.estc.dev', - }, - }, - ], - should: [], - must_not: [], - }, - }; - - expect(getThresholdSignalQueryFields(mockHit, mockFilters)).toEqual({ - 'event.dataset': 'traefik.access', - 'event.module': 'traefik', - 'traefik.access.entryPointName': 'web-secure', - 'url.domain': 'kibana.siem.estc.dev', - }); - }); - - it('should return proper fields object for nested match filters', () => { - const mockHit = { - ...sampleDocNoSortIdNoVersion(), - _source: { - '@timestamp': '2020-11-03T02:31:47.431Z', - event: { - dataset: 'traefik.access', - module: 'traefik', - }, - url: { - domain: 'kibana.siem.estc.dev', - }, - }, - }; - const filters = { - bool: { - must: [], - filter: [ - { - bool: { - filter: [ - { - bool: { - should: [ - { - match_phrase: { - 'event.module': 'traefik', - }, - }, - ], - minimum_should_match: 1, - }, - }, - { - bool: { - should: [ - { - match: { - 'event.dataset': 'traefik.*', - }, - }, - ], - minimum_should_match: 1, - }, - }, - ], - }, - }, - ], - should: [], - must_not: [], - }, - }; - - expect(getThresholdSignalQueryFields(mockHit, filters)).toEqual({ - 'event.dataset': 'traefik.access', - 'event.module': 'traefik', - }); - }); - - it('should return proper object for simple match filters', () => { - const mockHit = { - ...sampleDocNoSortIdNoVersion(), - _source: { - '@timestamp': '2020-11-03T02:31:47.431Z', - event: { - dataset: 'traefik.access', - module: 'traefik', - }, - }, - }; - const filters = { - bool: { - must: [], - filter: [ - { - bool: { - should: [ - { - match: { - 'event.module': 'traefik', - }, - }, - ], - minimum_should_match: 1, - }, - }, - { - match_phrase: { - 'event.dataset': 'traefik.access', - }, - }, - ], - should: [], - must_not: [], - }, - }; - - expect(getThresholdSignalQueryFields(mockHit, filters)).toEqual({ - 'event.dataset': 'traefik.access', - 'event.module': 'traefik', - }); - }); - - it('should return proper object for simple match_phrase filters', () => { - const mockHit = { - ...sampleDocNoSortIdNoVersion(), - _source: { - '@timestamp': '2020-11-03T02:31:47.431Z', - event: { - dataset: 'traefik.access', - module: 'traefik', - }, - }, +describe('transformThresholdResultsToEcs', () => { + it('should return transformed threshold results', () => { + const threshold = { + field: 'source.ip', + value: 1, }; - const filters = { - bool: { - must: [], - filter: [ - { - bool: { - should: [ - { - match_phrase: { - 'event.module': 'traefik', + const startedAt = new Date('2020-12-17T16:27:00Z'); + const transformedResults = transformThresholdResultsToEcs( + { + ...sampleDocSearchResultsNoSortId('abcd'), + aggregations: { + threshold: { + buckets: [ + { + key: '127.0.0.1', + doc_count: 1, + top_threshold_hits: { + hits: { + hits: [sampleDocNoSortId('abcd')], }, }, - ], - minimum_should_match: 1, - }, - }, - { - match_phrase: { - 'event.dataset': 'traefik.access', - }, + }, + ], }, - ], - should: [], - must_not: [], - }, - }; - - expect(getThresholdSignalQueryFields(mockHit, filters)).toEqual({ - 'event.module': 'traefik', - 'event.dataset': 'traefik.access', - }); - }); - - it('should return proper object for exists filters', () => { - const mockHit = { - ...sampleDocNoSortIdNoVersion(), - _source: { - '@timestamp': '2020-11-03T02:31:47.431Z', - event: { - module: 'traefik', }, }, - }; - const filters = { - bool: { - should: [ - { - bool: { - should: [ - { - exists: { - field: 'process.name', - }, - }, - ], - minimum_should_match: 1, - }, - }, - { - bool: { - should: [ - { - exists: { - field: 'event.type', - }, - }, - ], - minimum_should_match: 1, - }, - }, - ], - minimum_should_match: 1, + 'test', + startedAt, + undefined, + loggingSystemMock.createLogger(), + threshold, + '1234', + undefined + ); + const _id = calculateThresholdSignalUuid('1234', startedAt, 'source.ip', '127.0.0.1'); + expect(transformedResults).toEqual({ + took: 10, + timed_out: false, + _shards: { + total: 10, + successful: 10, + failed: 0, + skipped: 0, }, - }; - expect(getThresholdSignalQueryFields(mockHit, filters)).toEqual({}); - }); - - it('should NOT add invalid characters from CIDR such as the "/" proper object for simple match_phrase filters', () => { - const mockHit = { - ...sampleDocNoSortIdNoVersion(), - _source: { - '@timestamp': '2020-11-03T02:31:47.431Z', - destination: { - ip: '192.168.0.16', - }, - event: { - module: 'traefik', + results: { + hits: { + total: 1, }, }, - }; - const filters = { - bool: { - must: [], - filter: [ + hits: { + total: 100, + max_score: 100, + hits: [ { - bool: { - should: [ - { - match: { - 'destination.ip': '192.168.0.0/16', - }, - }, - ], - minimum_should_match: 1, + _id, + _index: 'test', + _source: { + '@timestamp': '2020-04-20T21:27:45+0000', + threshold_result: { + count: 1, + value: '127.0.0.1', + }, }, }, ], - should: [], - must_not: [], }, - }; - - expect(getThresholdSignalQueryFields(mockHit, filters)).toEqual({ - 'destination.ip': '192.168.0.16', }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts index a98aae4ec8107b..3cad33b2787495 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import uuidv5 from 'uuid/v5'; -import { reduce, get, isEmpty } from 'lodash/fp'; +import { get, isEmpty } from 'lodash/fp'; import set from 'set-value'; import { @@ -17,12 +16,10 @@ import { AlertServices } from '../../../../../alerts/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { RuleTypeParams, RefreshTypes } from '../types'; import { singleBulkCreate, SingleBulkCreateResponse } from './single_bulk_create'; -import { SignalSearchResponse, SignalSourceHit, ThresholdAggregationBucket } from './types'; +import { SignalSearchResponse, ThresholdAggregationBucket } from './types'; +import { calculateThresholdSignalUuid } from './utils'; import { BuildRuleMessage } from './rule_messages'; -// used to generate constant Threshold Signals ID when run with the same params -const NAMESPACE_ID = '0684ec03-7201-4ee0-8ee0-3a3f6b2479b2'; - interface BulkCreateThresholdSignalsParams { actions: RuleAlertAction[]; someResult: SignalSearchResponse; @@ -48,81 +45,6 @@ interface BulkCreateThresholdSignalsParams { buildRuleMessage: BuildRuleMessage; } -interface FilterObject { - bool?: { - filter?: FilterObject | FilterObject[]; - should?: Array>>; - }; -} - -const injectFirstMatch = ( - hit: SignalSourceHit, - match: object | Record -): Record | undefined => { - if (match != null) { - for (const key of Object.keys(match)) { - return { [key]: get(key, hit._source) } as Record; - } - } -}; - -const getNestedQueryFilters = ( - hit: SignalSourceHit, - filtersObj: FilterObject -): Record => { - if (Array.isArray(filtersObj.bool?.filter)) { - return reduce( - (acc, filterItem) => { - const nestedFilter = getNestedQueryFilters(hit, filterItem); - - if (nestedFilter) { - return { ...acc, ...nestedFilter }; - } - - return acc; - }, - {}, - filtersObj.bool?.filter - ); - } else { - return ( - (filtersObj.bool?.should && - filtersObj.bool?.should[0] && - (injectFirstMatch(hit, filtersObj.bool.should[0].match) || - injectFirstMatch(hit, filtersObj.bool.should[0].match_phrase))) ?? - {} - ); - } -}; - -export const getThresholdSignalQueryFields = (hit: SignalSourceHit, filter: unknown) => { - const filters = get('bool.filter', filter); - - return reduce( - (acc, item) => { - if (item.match_phrase) { - return { ...acc, ...injectFirstMatch(hit, item.match_phrase) }; - } - - if (item.bool?.should && (item.bool.should[0].match || item.bool.should[0].match_phrase)) { - return { - ...acc, - ...(injectFirstMatch(hit, item.bool.should[0].match) || - injectFirstMatch(hit, item.bool.should[0].match_phrase)), - }; - } - - if (item.bool?.filter) { - return { ...acc, ...getNestedQueryFilters(hit, item) }; - } - - return acc; - }, - {}, - filters - ); -}; - const getTransformedHits = ( results: SignalSearchResponse, inputIndex: string, @@ -153,13 +75,12 @@ const getTransformedHits = ( count: totalResults, value: ruleId, }, - ...getThresholdSignalQueryFields(hit, filter), }; return [ { _index: inputIndex, - _id: uuidv5(`${ruleId}${startedAt}${threshold.field}`, NAMESPACE_ID), + _id: calculateThresholdSignalUuid(ruleId, startedAt, threshold.field), _source: source, }, ]; @@ -183,14 +104,11 @@ const getTransformedHits = ( count: docCount, value: get(threshold.field, hit._source), }, - ...getThresholdSignalQueryFields(hit, filter), }; - set(source, threshold.field, key); - return { _index: inputIndex, - _id: uuidv5(`${ruleId}${startedAt}${threshold.field}${key}`, NAMESPACE_ID), + _id: calculateThresholdSignalUuid(ruleId, startedAt, threshold.field, key), _source: source, }; } @@ -226,6 +144,8 @@ export const transformThresholdResultsToEcs = ( }, }; + delete thresholdResults.aggregations; // no longer needed + set(thresholdResults, 'results.hits.total', transformedHits.length); return thresholdResults; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 7fd99a17598ae8..3928228357d4cb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -8,7 +8,6 @@ import { Logger, KibanaRequest } from 'src/core/server'; -import { Filter } from 'src/plugins/data/common'; import { SIGNALS_ID, DEFAULT_SEARCH_AFTER_PAGE_SIZE, @@ -29,7 +28,6 @@ import { SignalRuleAlertTypeDefinition, RuleAlertAttributes, EqlSignalSearchResponse, - ThresholdQueryBucket, WrappedSignalHit, } from './types'; import { @@ -48,9 +46,9 @@ import { signalParamsSchema } from './signal_params_schema'; import { siemRuleActionGroups } from './siem_rule_action_groups'; import { findMlSignals } from './find_ml_signals'; import { findThresholdSignals } from './find_threshold_signals'; -import { findPreviousThresholdSignals } from './find_previous_threshold_signals'; import { bulkCreateMlSignals } from './bulk_create_ml_signals'; import { bulkCreateThresholdSignals } from './bulk_create_threshold_signals'; +import { getThresholdBucketFilters } from './threshold_get_bucket_filters'; import { scheduleNotificationActions, NotificationRuleTypeParams, @@ -307,21 +305,11 @@ export const signalRulesAlertType = ({ ]); } else if (isThresholdRule(type) && threshold) { const inputIndex = await getInputIndex(services, version, index); - const esFilter = await getFilter({ - type, - filters, - language, - query, - savedId, - services, - index: inputIndex, - lists: exceptionItems ?? [], - }); const { - searchResult: previousSignals, + filters: bucketFilters, searchErrors: previousSearchErrors, - } = await findPreviousThresholdSignals({ + } = await getThresholdBucketFilters({ indexPattern: [outputIndex], from, to, @@ -333,29 +321,15 @@ export const signalRulesAlertType = ({ buildRuleMessage, }); - previousSignals.aggregations.threshold.buckets.forEach((bucket: ThresholdQueryBucket) => { - esFilter.bool.filter.push(({ - bool: { - must_not: { - bool: { - must: [ - { - term: { - [threshold.field || 'signal.rule.rule_id']: bucket.key, - }, - }, - { - range: { - [timestampOverride ?? '@timestamp']: { - lte: bucket.lastSignalTimestamp.value_as_string, - }, - }, - }, - ], - }, - }, - }, - } as unknown) as Filter); + const esFilter = await getFilter({ + type, + filters: filters ? filters.concat(bucketFilters) : bucketFilters, + language, + query, + savedId, + services, + index: inputIndex, + lists: exceptionItems ?? [], }); const { searchResult: thresholdResults, searchErrors } = await findThresholdSignals({ @@ -400,6 +374,7 @@ export const signalRulesAlertType = ({ tags, buildRuleMessage, }); + result = mergeReturns([ result, createSearchAfterReturnTypeFromResponse({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_previous_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_previous_threshold_signals.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts new file mode 100644 index 00000000000000..bf060da1e76b8f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEmpty } from 'lodash'; + +import { Filter } from 'src/plugins/data/common'; +import { ESFilter } from '../../../../../../typings/elasticsearch'; + +import { TimestampOverrideOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas'; +import { AlertServices } from '../../../../../alerts/server'; +import { Logger } from '../../../../../../../src/core/server'; +import { ThresholdQueryBucket } from './types'; +import { BuildRuleMessage } from './rule_messages'; +import { findPreviousThresholdSignals } from './threshold_find_previous_signals'; + +interface GetThresholdBucketFiltersParams { + from: string; + to: string; + indexPattern: string[]; + services: AlertServices; + logger: Logger; + ruleId: string; + bucketByField: string; + timestampOverride: TimestampOverrideOrUndefined; + buildRuleMessage: BuildRuleMessage; +} + +export const getThresholdBucketFilters = async ({ + from, + to, + indexPattern, + services, + logger, + ruleId, + bucketByField, + timestampOverride, + buildRuleMessage, +}: GetThresholdBucketFiltersParams): Promise<{ + filters: Filter[]; + searchErrors: string[]; +}> => { + const { searchResult, searchErrors } = await findPreviousThresholdSignals({ + indexPattern, + from, + to, + services, + logger, + ruleId, + bucketByField, + timestampOverride, + buildRuleMessage, + }); + + const filters = searchResult.aggregations.threshold.buckets.reduce( + (acc: ESFilter[], bucket: ThresholdQueryBucket): ESFilter[] => { + const filter = { + bool: { + filter: [ + { + range: { + [timestampOverride ?? '@timestamp']: { + lte: bucket.lastSignalTimestamp.value_as_string, + }, + }, + }, + ], + }, + } as ESFilter; + + if (!isEmpty(bucketByField)) { + (filter.bool.filter as ESFilter[]).push({ + term: { + [bucketByField]: bucket.key, + }, + }); + } + + return [...acc, filter]; + }, + [] as ESFilter[] + ); + + return { + filters: [ + ({ + bool: { + must_not: filters, + }, + } as unknown) as Filter, + ], + searchErrors, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index dd936776f691aa..073e30bbc6e26b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -36,6 +36,7 @@ import { mergeReturns, createTotalHitsFromSearchResult, lastValidDate, + calculateThresholdSignalUuid, } from './utils'; import { BulkResponseErrorAggregation, SearchAfterAndBulkCreateReturnType } from './types'; import { @@ -1303,4 +1304,18 @@ describe('utils', () => { expect(result).toEqual(4); }); }); + + describe('calculateThresholdSignalUuid', () => { + it('should generate a uuid without key', () => { + const startedAt = new Date('2020-12-17T16:27:00Z'); + const signalUuid = calculateThresholdSignalUuid('abcd', startedAt, 'agent.name'); + expect(signalUuid).toEqual('c0cbe4b7-48de-5734-ae81-d8de3e79839d'); + }); + + it('should generate a uuid with key', () => { + const startedAt = new Date('2019-11-18T13:32:00Z'); + const signalUuid = calculateThresholdSignalUuid('abcd', startedAt, 'host.ip', '1.2.3.4'); + expect(signalUuid).toEqual('f568509e-b570-5d3c-a7ed-7c73fd29ddaf'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 2114f21d9cead6..18f6e8d127b1b3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -5,6 +5,7 @@ */ import { createHash } from 'crypto'; import moment from 'moment'; +import uuidv5 from 'uuid/v5'; import dateMath from '@elastic/datemath'; import { TimestampOverrideOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas'; @@ -661,3 +662,20 @@ export const createTotalHitsFromSearchResult = ({ : searchResult.hits.total.value; return totalHits; }; + +export const calculateThresholdSignalUuid = ( + ruleId: string, + startedAt: Date, + thresholdField: string, + key?: string +): string => { + // used to generate constant Threshold Signals ID when run with the same params + const NAMESPACE_ID = '0684ec03-7201-4ee0-8ee0-3a3f6b2479b2'; + + let baseString = `${ruleId}${startedAt}${thresholdField}`; + if (key != null) { + baseString = `${baseString}${key}`; + } + + return uuidv5(baseString, NAMESPACE_ID); +}; diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/create_empty_failure_response.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/create_empty_failure_response.ts index 764ae5a87ec0ec..3980eef7caac20 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/create_empty_failure_response.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/create_empty_failure_response.ts @@ -6,10 +6,10 @@ import Boom, { Payload } from '@hapi/boom'; import { SavedObjectsImportError } from 'src/core/server'; -export const createEmptyFailureResponse = (errors?: Array) => { +export const createEmptyFailureResponse = (errors?: Array) => { const errorMessages: Array = (errors || []).map((error) => { if (Boom.isBoom(error as any)) { - return (error as Boom).output.payload as Payload; + return (error as Boom.Boom).output.payload as Payload; } return error as SavedObjectsImportError; }); diff --git a/x-pack/plugins/spaces/server/lib/errors.ts b/x-pack/plugins/spaces/server/lib/errors.ts index 13a5c2440877a8..0f6bf0f1d56b41 100644 --- a/x-pack/plugins/spaces/server/lib/errors.ts +++ b/x-pack/plugins/spaces/server/lib/errors.ts @@ -11,7 +11,7 @@ export function wrapError(error: any): CustomHttpResponseOptions const boom = isBoom(error) ? error : boomify(error); return { body: boom, - headers: boom.output.headers, + headers: boom.output.headers as { [key: string]: string }, statusCode: boom.output.statusCode, }; } diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/index.ts index 59effdbf8f512b..ba490b91fae103 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/index.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/index.ts @@ -12,9 +12,6 @@ import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; export function getAlertType(): AlertTypeModel { return { id: '.geo-containment', - name: i18n.translate('xpack.stackAlerts.geoContainment.name.trackingContainment', { - defaultMessage: 'Tracking containment', - }), description: i18n.translate('xpack.stackAlerts.geoContainment.descriptionText', { defaultMessage: 'Alert when an entity is contained within a geo boundary.', }), diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/index.ts index cc8d78b53137ea..8ba632633a3af0 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/index.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/index.ts @@ -12,9 +12,6 @@ import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; export function getAlertType(): AlertTypeModel { return { id: '.geo-threshold', - name: i18n.translate('xpack.stackAlerts.geoThreshold.name.trackingThreshold', { - defaultMessage: 'Tracking threshold', - }), description: i18n.translate('xpack.stackAlerts.geoThreshold.descriptionText', { defaultMessage: 'Alert when an entity enters or leaves a geo boundary.', }), diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/threshold/index.ts index f09d1630cd6751..184277bae3da86 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/index.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/index.ts @@ -12,9 +12,6 @@ import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; export function getAlertType(): AlertTypeModel { return { id: '.index-threshold', - name: i18n.translate('xpack.stackAlerts.threshold.ui.alertType.nameText', { - defaultMessage: 'Index threshold', - }), description: i18n.translate('xpack.stackAlerts.threshold.ui.alertType.descriptionText', { defaultMessage: 'Alert when an aggregated query meets the threshold.', }), diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts index 164ce993eebac0..51d7361bfe762b 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts @@ -114,7 +114,7 @@ export interface GeoContainmentParams { export function getAlertType(logger: Logger): AlertType { const alertTypeName = i18n.translate('xpack.stackAlerts.geoContainment.alertTypeTitle', { - defaultMessage: 'Geo tracking containment', + defaultMessage: 'Tracking containment', }); const actionGroupName = i18n.translate( diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/alert_type.test.ts index f3dc3855eb91bf..0592c944de570e 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/alert_type.test.ts @@ -14,7 +14,7 @@ describe('alertType', () => { it('alert type creation structure is the expected value', async () => { expect(alertType.id).toBe('.geo-containment'); - expect(alertType.name).toBe('Geo tracking containment'); + expect(alertType.name).toBe('Tracking containment'); expect(alertType.actionGroups).toEqual([ { id: 'Tracked entity contained', name: 'Tracking containment met' }, ]); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts index 93a6c0d29cf3cf..bf5e2fe2289db8 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts @@ -174,7 +174,7 @@ export interface GeoThresholdParams { export function getAlertType(logger: Logger): AlertType { const alertTypeName = i18n.translate('xpack.stackAlerts.geoThreshold.alertTypeTitle', { - defaultMessage: 'Geo tracking threshold', + defaultMessage: 'Tracking threshold', }); const actionGroupName = i18n.translate( diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/alert_type.test.ts index 49b56b5571b441..0cfce2d47f1898 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/alert_type.test.ts @@ -14,7 +14,7 @@ describe('alertType', () => { it('alert type creation structure is the expected value', async () => { expect(alertType.id).toBe('.geo-threshold'); - expect(alertType.name).toBe('Geo tracking threshold'); + expect(alertType.name).toBe('Tracking threshold'); expect(alertType.actionGroups).toEqual([ { id: 'tracking threshold met', name: 'Tracking threshold met' }, ]); diff --git a/x-pack/plugins/stack_alerts/server/plugin.test.ts b/x-pack/plugins/stack_alerts/server/plugin.test.ts index 3037504ed3e39b..0f747e9c24eec1 100644 --- a/x-pack/plugins/stack_alerts/server/plugin.test.ts +++ b/x-pack/plugins/stack_alerts/server/plugin.test.ts @@ -63,7 +63,7 @@ describe('AlertingBuiltins Plugin', () => { }, ], "id": ".geo-threshold", - "name": "Geo tracking threshold", + "name": "Tracking threshold", } `); diff --git a/x-pack/plugins/transform/server/routes/api/error_utils.ts b/x-pack/plugins/transform/server/routes/api/error_utils.ts index 4986eb718dc2c0..356158913eb925 100644 --- a/x-pack/plugins/transform/server/routes/api/error_utils.ts +++ b/x-pack/plugins/transform/server/routes/api/error_utils.ts @@ -79,7 +79,7 @@ export function wrapError(error: any): CustomHttpResponseOptions const boom = Boom.isBoom(error) ? error : Boom.boomify(error, { statusCode: error.statusCode }); return { body: boom, - headers: boom.output.headers, + headers: boom.output.headers as { [key: string]: string }, statusCode: boom.output.statusCode, }; } @@ -130,7 +130,6 @@ export function wrapEsError(err: any, statusCodeToMessageMap: Record = ({ size }) => ( + + + + + +); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx index 0f20ade8187fdb..66f7c1d36dfb21 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx @@ -9,7 +9,7 @@ import { Option, none, some, fold } from 'fp-ts/lib/Option'; import { pipe } from 'fp-ts/lib/pipeable'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiLink, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiLink, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { EuiEmptyPrompt, EuiCode } from '@elastic/eui'; @@ -19,6 +19,7 @@ import { health } from '../lib/alert_api'; import './health_check.scss'; import { useHealthContext } from '../context/health_context'; import { useKibana } from '../../common/lib/kibana'; +import { CenterJustifiedSpinner } from './center_justified_spinner'; interface Props { inFlyout?: boolean; @@ -47,7 +48,15 @@ export const HealthCheck: React.FunctionComponent = ({ return pipe( alertingHealth, fold( - () => (waitForCheck ? : {children}), + () => + waitForCheck ? ( + + + + + ) : ( + {children} + ), (healthCheck) => { return healthCheck?.isSufficientlySecure && healthCheck?.hasPermanentEncryptionKey ? ( {children} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_type_compare.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_type_compare.test.ts index e364661361814f..0cd5118c5e316b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_type_compare.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_type_compare.test.ts @@ -30,7 +30,6 @@ test('should sort groups by containing enabled alert types first and then by nam alertTypeItem: { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { @@ -52,7 +51,6 @@ test('should sort groups by containing enabled alert types first and then by nam alertTypeItem: { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { @@ -69,7 +67,6 @@ test('should sort groups by containing enabled alert types first and then by nam alertTypeItem: { id: 'disabled-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { @@ -91,7 +88,6 @@ test('should sort groups by containing enabled alert types first and then by nam alertTypeItem: { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { @@ -130,7 +126,6 @@ test('should sort alert types by enabled first and then by name', async () => { alertTypeItem: { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { @@ -147,7 +142,6 @@ test('should sort alert types by enabled first and then by name', async () => { alertTypeItem: { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { @@ -164,7 +158,6 @@ test('should sort alert types by enabled first and then by name', async () => { alertTypeItem: { id: 'disabled-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx b/x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx index 563353793f991f..98c20c5abcc2de 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx @@ -4,23 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { Suspense } from 'react'; -import { EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { EuiLoadingSpinnerSize } from '@elastic/eui/src/components/loading/loading_spinner'; +import { CenterJustifiedSpinner } from '../components/center_justified_spinner'; export function suspendedComponentWithProps( ComponentToSuspend: React.ComponentType, size?: EuiLoadingSpinnerSize ) { return (props: T) => ( - - - - - - } - > + }> ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx index 7d8949421126c9..a83194d67a759b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx @@ -12,9 +12,6 @@ import { EuiSpacer, EuiFieldText, EuiFormRow, - EuiLoadingSpinner, - EuiFlexGroup, - EuiFlexItem, EuiErrorBoundary, EuiTitle, } from '@elastic/eui'; @@ -29,6 +26,7 @@ import { } from '../../../types'; import { hasSaveActionsCapability } from '../../lib/capabilities'; import { useKibana } from '../../../common/lib/kibana'; +import { SectionLoading } from '../../components/section_loading'; export function validateBaseProperties(actionObject: ActionConnector) { const validationResult = { errors: {} }; @@ -181,11 +179,12 @@ export const ActionConnectorForm = ({ - - - - + + + } > {ParamsFieldsComponent ? ( - - - - - - } - > + void; @@ -31,6 +33,7 @@ export const ActionTypeMenu = ({ http, notifications: { toasts }, } = useKibana().services; + const [loadingActionTypes, setLoadingActionTypes] = useState(false); const [actionTypesIndex, setActionTypesIndex] = useState(undefined); useEffect(() => { @@ -43,11 +46,14 @@ export const ActionTypeMenu = ({ * * TODO: Remove when cases connector is available across Kibana. Issue: https://github.com/elastic/kibana/issues/82502. * */ - const availableActionTypes = - actionTypes ?? - (await loadActionTypes({ http })).filter( + let availableActionTypes = actionTypes; + if (!availableActionTypes) { + setLoadingActionTypes(true); + availableActionTypes = (await loadActionTypes({ http })).filter( (actionType) => !DEFAULT_HIDDEN_ACTION_TYPES.includes(actionType.id) ); + setLoadingActionTypes(false); + } const index: ActionTypeIndex = {}; for (const actionTypeItem of availableActionTypes) { index[actionTypeItem.id] = actionTypeItem; @@ -117,7 +123,14 @@ export const ActionTypeMenu = ({ ); }); - return ( + return loadingActionTypes ? ( + + + + ) : (
    diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index 2df75436f5f96b..bf6786d0d4e4ca 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -10,7 +10,6 @@ import { EuiSpacer, EuiButton, EuiLink, - EuiLoadingSpinner, EuiIconTip, EuiFlexGroup, EuiFlexItem, @@ -40,6 +39,7 @@ import { ActionConnector, ActionConnectorTableItem, ActionTypeIndex } from '../. import { EmptyConnectorsPrompt } from '../../../components/prompts/empty_connectors_prompt'; import { useKibana } from '../../../../common/lib/kibana'; import { DEFAULT_HIDDEN_ACTION_TYPES } from '../../../../'; +import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; export const ActionsConnectorsList: React.FunctionComponent = () => { const { @@ -355,13 +355,7 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { /> {/* Render the view based on if there's data or if they can save */} - {(isLoadingActions || isLoadingActionTypes) && ( - - - - - - )} + {(isLoadingActions || isLoadingActionTypes) && } {actionConnectorTableItems.length !== 0 && table} {actionConnectorTableItems.length === 0 && canSave && diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index e25e703de5f7ee..30ca2c620f1d7c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -657,7 +657,6 @@ describe('edit button', () => { const alertTypeR: AlertTypeModel = { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx index 48360647e24ee5..7a12c43427a913 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx @@ -10,7 +10,7 @@ import { createMemoryHistory, createLocation } from 'history'; import { ToastsApi } from 'kibana/public'; import { AlertDetailsRoute, getAlertData } from './alert_details_route'; import { Alert } from '../../../../types'; -import { EuiLoadingSpinner } from '@elastic/eui'; +import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; jest.mock('../../../../common/lib/kibana'); describe('alert_details_route', () => { @@ -20,7 +20,7 @@ describe('alert_details_route', () => { expect( shallow( - ).containsMatchingElement() + ).containsMatchingElement() ).toBeTruthy(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.tsx index fc3e05fbfaed0e..ae729dd4f0095e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.tsx @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import React, { useState, useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { EuiLoadingSpinner } from '@elastic/eui'; import { ToastsApi } from 'kibana/public'; import { Alert, AlertType, ActionType } from '../../../../types'; import { AlertDetailsWithApi as AlertDetails } from './alert_details'; @@ -21,6 +20,7 @@ import { withActionOperations, } from '../../common/components/with_actions_api_operations'; import { useKibana } from '../../../../common/lib/kibana'; +import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; type AlertDetailsRouteProps = RouteComponentProps<{ alertId: string; @@ -66,14 +66,7 @@ export const AlertDetailsRoute: React.FunctionComponent requestRefresh={async () => requestRefresh(Date.now())} /> ) : ( -
    - -
    + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx index e3fe9cd86356ae..dfaed32ff72aea 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx @@ -9,7 +9,7 @@ import { shallow } from 'enzyme'; import { ToastsApi } from 'kibana/public'; import { AlertInstancesRoute, getAlertInstanceSummary } from './alert_instances_route'; import { Alert, AlertInstanceSummary, AlertType } from '../../../../types'; -import { EuiLoadingSpinner } from '@elastic/eui'; +import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; jest.mock('../../../../common/lib/kibana'); const fakeNow = new Date('2020-02-09T23:15:41.941Z'); @@ -23,7 +23,7 @@ describe('alert_instance_summary_route', () => { expect( shallow( - ).containsMatchingElement() + ).containsMatchingElement() ).toBeTruthy(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx index e1e0866d886a30..a122d59959156f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import { ToastsApi } from 'kibana/public'; import React, { useState, useEffect } from 'react'; -import { EuiLoadingSpinner } from '@elastic/eui'; import { Alert, AlertInstanceSummary, AlertType } from '../../../../types'; import { ComponentOpts as AlertApis, @@ -15,6 +14,7 @@ import { } from '../../common/components/with_bulk_alert_api_operations'; import { AlertInstancesWithApi as AlertInstances } from './alert_instances'; import { useKibana } from '../../../../common/lib/kibana'; +import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; type WithAlertInstanceSummaryProps = { alert: Alert; @@ -52,14 +52,7 @@ export const AlertInstancesRoute: React.FunctionComponent ) : ( -
    - -
    + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx index 2790ea8aa6bfa4..6057d2669f04c9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx @@ -94,7 +94,6 @@ describe('alert_add', () => { const alertType = { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'test', documentationUrl: null, validate: (): ValidationResult => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx index 25f830df58df53..e5a6a8977a8c8c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx @@ -52,7 +52,6 @@ describe('alert_edit', () => { const alertType = { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'test', documentationUrl: null, validate: (): ValidationResult => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx index d41ca915f34c11..ef8d17d8c4c282 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx @@ -26,7 +26,6 @@ describe('alert_form', () => { const alertType = { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: (): ValidationResult => { @@ -54,7 +53,6 @@ describe('alert_form', () => { const alertTypeNonEditable = { id: 'non-edit-alert-type', iconClass: 'test', - name: 'non edit alert', description: 'test', documentationUrl: null, validate: (): ValidationResult => { @@ -67,7 +65,6 @@ describe('alert_form', () => { const disabledByLicenseAlertType = { id: 'disabled-by-license', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: (): ValidationResult => { @@ -306,7 +303,6 @@ describe('alert_form', () => { { id: 'same-consumer-producer-alert-type', iconClass: 'test', - name: 'test-alert', description: 'test', documentationUrl: null, validate: (): ValidationResult => { @@ -318,7 +314,6 @@ describe('alert_form', () => { { id: 'other-consumer-producer-alert-type', iconClass: 'test', - name: 'test-alert', description: 'test', documentationUrl: null, validate: (): ValidationResult => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 3210d538419937..a67fd218d55f34 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, useState, useEffect, Suspense, useCallback } from 'react'; +import React, { Fragment, useState, useEffect, useCallback, Suspense } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -23,7 +23,6 @@ import { EuiIconTip, EuiButtonIcon, EuiHorizontalRule, - EuiLoadingSpinner, EuiEmptyPrompt, EuiListGroupItem, EuiListGroup, @@ -71,6 +70,7 @@ import { AlertNotifyWhen } from './alert_notify_when'; import { checkAlertTypeEnabled } from '../../lib/check_alert_type_enabled'; import { alertTypeCompare, alertTypeGroupCompare } from '../../lib/alert_type_compare'; import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants'; +import { SectionLoading } from '../../components/section_loading'; const ENTER_KEY = 13; @@ -289,10 +289,7 @@ export const AlertForm = ({ ) .filter((alertTypeItem) => searchValue - ? alertTypeItem.alertTypeModel.name - .toString() - .toLocaleLowerCase() - .includes(searchValue) || + ? alertTypeItem.alertType.name.toString().toLocaleLowerCase().includes(searchValue) || alertTypeItem.alertType!.producer.toLocaleLowerCase().includes(searchValue) || alertTypeItem.alertTypeModel.description.toLocaleLowerCase().includes(searchValue) : alertTypeItem @@ -378,10 +375,7 @@ export const AlertForm = ({ hasDisabledByLicenseAlertTypes = true; } (result[producer] = result[producer] || []).push({ - name: - typeof alertTypeValue.alertTypeModel.name === 'string' - ? alertTypeValue.alertTypeModel.name - : alertTypeValue.alertTypeModel.name.props.defaultMessage, + name: alertTypeValue.alertType.name, id: alertTypeValue.alertTypeModel.id, checkEnabledResult, alertTypeItem: alertTypeValue.alertTypeModel, @@ -475,11 +469,9 @@ export const AlertForm = ({
    - + {alert.alertTypeId && alertTypesIndex && alertTypesIndex.has(alert.alertTypeId) + ? alertTypesIndex.get(alert.alertTypeId)!.name + : ''}
    @@ -535,7 +527,16 @@ export const AlertForm = ({ alert.alertTypeId && selectedAlertType ? ( - }> + + + + } + > ) : ( - + + + )} ); }; -const CenterJustifiedSpinner = () => ( - - - - - -); - const NoAuthorizedAlertTypes = ({ operation }: { operation: string }) => ( { {loadedItems.length || isFilterApplied ? ( table ) : alertTypesState.isLoading || alertsState.isLoading ? ( - - - - - + ) : authorizedToCreateAnyAlerts ? ( setAlertFlyoutVisibility(true)} /> ) : ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts index f875bcabdcde82..aa61fcde9e9c25 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts @@ -11,10 +11,9 @@ export const ExpressionComponent: React.FunctionComponent = () => { return null; }; -const getTestAlertType = (id?: string, name?: string, iconClass?: string) => { +const getTestAlertType = (id?: string, iconClass?: string) => { return { id: id || 'test-alet-type', - name: name || 'Test alert type', description: 'Test description', iconClass: iconClass || 'icon', documentationUrl: null, diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index cd1ebe47a8c229..3fffe9fe230b4b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -187,7 +187,6 @@ export interface AlertTypeParamsExpressionProps< export interface AlertTypeModel { id: string; - name: string | JSX.Element; description: string; iconClass: string; documentationUrl: string | ((docLinks: DocLinksStart) => string) | null; diff --git a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx index 9bb506b3ebf148..77362752f6960f 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx @@ -11,13 +11,12 @@ import { ActionWizard } from './action_wizard'; import { ActionFactory, ActionFactoryDefinition, BaseActionConfig } from '../../dynamic_actions'; import { CollectConfigProps } from '../../../../../../src/plugins/kibana_utils/public'; import { licensingMock } from '../../../../licensing/public/mocks'; +import { Trigger, TriggerId } from '../../../../../../src/plugins/ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../../../../../../src/plugins/data/public'; import { - APPLY_FILTER_TRIGGER, SELECT_RANGE_TRIGGER, - Trigger, - TriggerId, VALUE_CLICK_TRIGGER, -} from '../../../../../../src/plugins/ui_actions/public'; +} from '../../../../../../src/plugins/embeddable/public'; export const dashboards = [ { id: 'dashboard1', title: 'Dashboard 1' }, diff --git a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts index 8da45276fa532f..7e297c1cb6d7bc 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts +++ b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts @@ -207,11 +207,6 @@ describe('monitor status alert type', () => { "documentationUrl": [Function], "iconClass": "uptimeApp", "id": "xpack.uptime.alerts.monitorStatus", - "name": , "requiresAppContext": false, "validate": [Function], } diff --git a/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx b/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx index e02cc11269e9cb..39a8a36a6d0a89 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx @@ -10,7 +10,7 @@ import { CLIENT_ALERT_TYPES } from '../../../common/constants/alerts'; import { DurationAnomalyTranslations } from './translations'; import { AlertTypeInitializer } from '.'; -const { name, defaultActionMessage, description } = DurationAnomalyTranslations; +const { defaultActionMessage, description } = DurationAnomalyTranslations; const DurationAnomalyAlert = React.lazy(() => import('./lazy_wrapper/duration_anomaly')); export const initDurationAnomalyAlertType: AlertTypeInitializer = ({ @@ -25,7 +25,6 @@ export const initDurationAnomalyAlertType: AlertTypeInitializer = ({ alertParamsExpression: (params: unknown) => ( ), - name, description, validate: () => ({ errors: {} }), defaultActionMessage, diff --git a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx index 43aaa26d86642a..6a00d2987f12b3 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; import { AlertTypeModel, ValidationResult } from '../../../../triggers_actions_ui/public'; import { AlertTypeInitializer } from '.'; @@ -23,12 +22,6 @@ export const initMonitorStatusAlertType: AlertTypeInitializer = ({ plugins, }): AlertTypeModel => ({ id: CLIENT_ALERT_TYPES.MONITOR_STATUS, - name: ( - - ), description, iconClass: 'uptimeApp', documentationUrl(docLinks) { diff --git a/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx b/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx index 83c4792e26f597..43e5b75aa5f8b2 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx @@ -10,7 +10,7 @@ import { CLIENT_ALERT_TYPES } from '../../../common/constants/alerts'; import { TlsTranslations } from './translations'; import { AlertTypeInitializer } from '.'; -const { name, defaultActionMessage, description } = TlsTranslations; +const { defaultActionMessage, description } = TlsTranslations; const TLSAlert = React.lazy(() => import('./lazy_wrapper/tls_alert')); export const initTlsAlertType: AlertTypeInitializer = ({ core, plugins }): AlertTypeModel => ({ id: CLIENT_ALERT_TYPES.TLS, @@ -21,7 +21,6 @@ export const initTlsAlertType: AlertTypeInitializer = ({ core, plugins }): Alert alertParamsExpression: (params: any) => ( ), - name, description, validate: () => ({ errors: {} }), defaultActionMessage, diff --git a/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts b/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts index 5d22e22ee0eb6f..5d3a2c105c4a45 100644 --- a/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts +++ b/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts @@ -24,6 +24,12 @@ import { registerSettingsRoute } from './settings'; type HttpService = ReturnType; type HttpSetup = UnwrapPromise>; +export function mockGetClusterInfo(clusterInfo: any) { + const esClient = elasticsearchServiceMock.createScopedClusterClient().asCurrentUser; + // @ts-ignore we only care about the response body + esClient.info.mockResolvedValue({ body: { ...clusterInfo } }); + return esClient; +} describe('/api/settings', () => { let server: HttpService; let httpSetup: HttpSetup; @@ -31,7 +37,7 @@ describe('/api/settings', () => { let mockApiCaller: jest.Mocked; beforeEach(async () => { - mockApiCaller = jest.fn().mockResolvedValue({ cluster_uuid: 'yyy-yyyyy' }); + mockApiCaller = jest.fn(); server = createHttpServer(); httpSetup = await server.setup({ context: contextServiceMock.createSetupContract({ @@ -43,7 +49,7 @@ describe('/api/settings', () => { }, }, client: { - asCurrentUser: elasticsearchServiceMock.createScopedClusterClient().asCurrentUser, + asCurrentUser: mockGetClusterInfo({ cluster_uuid: 'yyy-yyyyy' }), }, }, savedObjects: { diff --git a/x-pack/plugins/xpack_legacy/server/routes/settings.ts b/x-pack/plugins/xpack_legacy/server/routes/settings.ts index 9a30ca30616b75..93dc6898f0c2e7 100644 --- a/x-pack/plugins/xpack_legacy/server/routes/settings.ts +++ b/x-pack/plugins/xpack_legacy/server/routes/settings.ts @@ -58,9 +58,9 @@ export function registerSettingsRoute({ const settings = (await settingsCollector.fetch(collectorFetchContext)) ?? settingsCollector.getEmailValueStructure(null); - const { cluster_uuid: uuid } = await callAsCurrentUser('info', { - filterPath: 'cluster_uuid', - }); + + const { body } = await collectorFetchContext.esClient.info({ filter_path: 'cluster_uuid' }); + const uuid: string = body.cluster_uuid; const overallStatus = await overallStatus$.pipe(first()).toPromise(); @@ -76,7 +76,6 @@ export function registerSettingsRoute({ snapshot: SNAPSHOT_REGEX.test(config.kibanaVersion), status: ServiceStatusToLegacyState[overallStatus.level.toString()], }; - return res.ok({ body: { cluster_uuid: uuid, diff --git a/x-pack/test/api_integration/apis/ml/saved_objects/can_delete_job.ts b/x-pack/test/api_integration/apis/ml/saved_objects/can_delete_job.ts index d95c90d417203a..e80d5a333bbdf0 100644 --- a/x-pack/test/api_integration/apis/ml/saved_objects/can_delete_job.ts +++ b/x-pack/test/api_integration/apis/ml/saved_objects/can_delete_job.ts @@ -75,7 +75,7 @@ export default ({ getService }: FtrProviderContext) => { idSpace1 ); - expect(body).to.eql({ [adJobIdSpace12]: { canDelete: false, canUntag: true } }); + expect(body).to.eql({ [adJobIdSpace12]: { canDelete: false, canRemoveFromSpace: true } }); }); it('job in individual spaces, all spaces user can delete and untag', async () => { @@ -87,7 +87,7 @@ export default ({ getService }: FtrProviderContext) => { idSpace1 ); - expect(body).to.eql({ [adJobIdSpace12]: { canDelete: true, canUntag: true } }); + expect(body).to.eql({ [adJobIdSpace12]: { canDelete: true, canRemoveFromSpace: true } }); }); it('job in * space, single space user can not untag or delete', async () => { @@ -99,7 +99,7 @@ export default ({ getService }: FtrProviderContext) => { idSpace1 ); - expect(body).to.eql({ [adJobIdStarSpace]: { canDelete: false, canUntag: false } }); + expect(body).to.eql({ [adJobIdStarSpace]: { canDelete: false, canRemoveFromSpace: false } }); }); it('job in * space, all spaces user can delete but not untag', async () => { @@ -111,7 +111,7 @@ export default ({ getService }: FtrProviderContext) => { idStarSpace ); - expect(body).to.eql({ [adJobIdStarSpace]: { canDelete: true, canUntag: false } }); + expect(body).to.eql({ [adJobIdStarSpace]: { canDelete: true, canRemoveFromSpace: false } }); }); }); }; diff --git a/x-pack/test/fleet_api_integration/apis/index.js b/x-pack/test/fleet_api_integration/apis/index.js index 5b230e5a179a54..0d634f60e282f2 100644 --- a/x-pack/test/fleet_api_integration/apis/index.js +++ b/x-pack/test/fleet_api_integration/apis/index.js @@ -7,8 +7,6 @@ export default function ({ loadTestFile }) { describe('Fleet Endpoints', function () { this.tags('ciGroup10'); - // Fleet setup - loadTestFile(require.resolve('./setup')); // Agent setup loadTestFile(require.resolve('./agents_setup')); // Agents diff --git a/x-pack/test/fleet_api_integration/apis/setup.ts b/x-pack/test/fleet_api_integration/apis/setup.ts deleted file mode 100644 index 4d1562e703770a..00000000000000 --- a/x-pack/test/fleet_api_integration/apis/setup.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../api_integration/ftr_provider_context'; - -export default function (providerContext: FtrProviderContext) { - const { getService } = providerContext; - const supertest = getService('supertest'); - const es = getService('es'); - describe('Fleet setup', async () => { - before(async () => { - await supertest.post(`/api/fleet/setup`).set('kbn-xsrf', 'xxx').send(); - }); - - it('should have installed placeholder indices', async function () { - const resLogsIndexPatternPlaceholder = await es.transport.request({ - method: 'GET', - path: `/logs-index_pattern_placeholder`, - }); - expect(resLogsIndexPatternPlaceholder.statusCode).equal(200); - const resMetricsIndexPatternPlaceholder = await es.transport.request({ - method: 'GET', - path: `/metrics-index_pattern_placeholder`, - }); - expect(resMetricsIndexPatternPlaceholder.statusCode).equal(200); - }); - }); -} diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts index af4aedda06ef75..50de66ac1c3baf 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts @@ -28,7 +28,6 @@ export class AlertingFixturePlugin implements Plugin { + await pageObjects.hosts.navigateToSecurityHostsPage(); + await pageObjects.common.dismissBanner(); + const fromTime = 'Jan 1, 2018 @ 00:00:00.000'; + const toTime = 'now'; + await pageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + }; + + describe.skip('Endpoint Event Resolver', function () { before(async () => { - await pageObjects.hosts.navigateToSecurityHostsPage(); - await pageObjects.common.dismissBanner(); - const fromTime = 'Jan 1, 2018 @ 00:00:00.000'; - const toTime = 'now'; - await pageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await browser.setWindowSize(1800, 1200); }); - describe.skip('Endpoint Resolver Tree', function () { + after(async () => { + await pageObjects.hosts.deleteDataStreams(); + }); + + describe('Endpoint Resolver Tree', function () { before(async () => { await esArchiver.load('empty_kibana'); await esArchiver.load('endpoint/resolver_tree/functions', { useCreate: true }); + await navigateToHostsAndSetDate(); await pageObjects.hosts.executeQueryAndOpenResolver('event.dataset : endpoint.events.file'); }); after(async () => { @@ -213,6 +226,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { before(async () => { await esArchiver.load('empty_kibana'); await esArchiver.load('endpoint/resolver_tree/alert_events', { useCreate: true }); + await navigateToHostsAndSetDate(); }); after(async () => { await pageObjects.hosts.deleteDataStreams(); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts index 220d932787fffd..3f27d1868461f4 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts @@ -277,7 +277,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ filter: entityIDFilter, - indexPatterns: ['metrics-*'], + indexPatterns: ['doesnotexist-*'], timeRange: { from: tree.startTime, to: tree.endTime, diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/tree.ts b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/tree.ts index 9a731f1d5aee0f..ab6cac7f357a06 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/tree.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/tree.ts @@ -281,7 +281,7 @@ export default function ({ getService }: FtrProviderContext) { from: tree.startTime.toISOString(), to: tree.endTime.toISOString(), }, - indexPatterns: ['metrics-*'], + indexPatterns: ['doesnotexist-*'], }) .expect(200); expect(body).to.be.empty(); diff --git a/yarn.lock b/yarn.lock index 4dbfa610be6c39..6e4df2f5b197a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1765,18 +1765,13 @@ normalize-path "^2.0.1" through2 "^2.0.3" -"@hapi/accept@^3.2.4": - version "3.2.4" - resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-3.2.4.tgz#687510529493fe1d7d47954c31aff360d9364bd1" - integrity sha512-soThGB+QMgfxlh0Vzhzlf3ZOEOPk5biEwcOXhkF0Eedqx8VnhGiggL9UYHrIsOb1rUg3Be3K8kp0iDL2wbVSOQ== +"@hapi/accept@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.1.tgz#068553e867f0f63225a506ed74e899441af53e10" + integrity sha512-fMr4d7zLzsAXo28PRRQPXR1o2Wmu+6z+VY1UzDp0iFo13Twj8WePakwXBiqn3E1aAlTpSNzCXdnnQXFhst8h8Q== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/hoek" "8.x.x" - -"@hapi/address@2.x.x", "@hapi/address@^2.1.2": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" - integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" "@hapi/address@^4.1.0": version "4.1.0" @@ -1785,225 +1780,194 @@ dependencies: "@hapi/hoek" "^9.0.0" -"@hapi/ammo@3.x.x", "@hapi/ammo@^3.1.2": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@hapi/ammo/-/ammo-3.1.2.tgz#a9edf5d48d99b75fdcd7ab3dabf9059942a06961" - integrity sha512-ej9OtFmiZv1qr45g1bxEZNGyaR4jRpyMxU6VhbxjaYThymvOwsyIsUKMZnP5Qw2tfYFuwqCJuIBHGpeIbdX9gQ== +"@hapi/ammo@5.x.x", "@hapi/ammo@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@hapi/ammo/-/ammo-5.0.1.tgz#9d34560f5c214eda563d838c01297387efaab490" + integrity sha512-FbCNwcTbnQP4VYYhLNGZmA76xb2aHg9AMPiy18NZyWMG310P5KdFGyA9v2rm5ujrIny77dEEIkMOwl0Xv+fSSA== dependencies: - "@hapi/hoek" "8.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/b64@4.x.x": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-4.2.1.tgz#bf8418d7907c5e73463f2e3b5c6fca7e9f2a1357" - integrity sha512-zqHpQuH5CBMw6hADzKfU/IGNrxq1Q+/wTYV+OiZRQN9F3tMyk+9BUMeBvFRMamduuqL8iSp62QAnJ+7ATiYLWA== +"@hapi/b64@5.x.x": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-5.0.0.tgz#b8210cbd72f4774985e78569b77e97498d24277d" + integrity sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw== dependencies: - "@hapi/hoek" "8.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/boom@7.x.x", "@hapi/boom@^7.4.11": - version "7.4.11" - resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-7.4.11.tgz#37af8417eb9416aef3367aa60fa04a1a9f1fc262" - integrity sha512-VSU/Cnj1DXouukYxxkes4nNJonCnlogHvIff1v1RVoN4xzkKhMXX+GRmb3NyH1iar10I9WFPDv2JPwfH3GaV0A== +"@hapi/boom@9.x.x", "@hapi/boom@^9.0.0", "@hapi/boom@^9.1.1": + version "9.1.1" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.1.tgz#89e6f0e01637c2a4228da0d113e8157c93677b04" + integrity sha512-VNR8eDbBrOxBgbkddRYIe7+8DZ+vSbV6qlmaN2x7eWjsUjy2VmQgChkOKcVZIeupEZYj+I0dqNg430OhwzagjA== dependencies: - "@hapi/hoek" "8.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/bounce@1.x.x": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@hapi/bounce/-/bounce-1.3.2.tgz#3b096bb02f67de6115e6e4f0debc390be5a86bad" - integrity sha512-3bnb1AlcEByFZnpDIidxQyw1Gds81z/1rSqlx4bIEE+wUN0ATj0D49B5cE1wGocy90Rp/de4tv7GjsKd5RQeew== +"@hapi/bounce@2.x.x": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/bounce/-/bounce-2.0.0.tgz#e6ef56991c366b1e2738b2cd83b01354d938cf3d" + integrity sha512-JesW92uyzOOyuzJKjoLHM1ThiOvHPOLDHw01YV8yh5nCso7sDwJho1h0Ad2N+E62bZyz46TG3xhAi/78Gsct6A== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/hoek" "^8.3.1" + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/bourne@1.x.x": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a" - integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA== +"@hapi/bourne@2.x.x": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-2.0.0.tgz#5bb2193eb685c0007540ca61d166d4e1edaf918d" + integrity sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg== -"@hapi/call@^5.1.3": - version "5.1.3" - resolved "https://registry.yarnpkg.com/@hapi/call/-/call-5.1.3.tgz#217af45e3bc3d38b03aa5c9edfe1be939eee3741" - integrity sha512-5DfWpMk7qZiYhvBhM5oUiT4GQ/O8a2rFR121/PdwA/eZ2C1EsuD547ZggMKAR5bZ+FtxOf0fdM20zzcXzq2mZA== +"@hapi/call@8.x.x": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@hapi/call/-/call-8.0.1.tgz#9e64cd8ba6128eb5be6e432caaa572b1ed8cd7c0" + integrity sha512-bOff6GTdOnoe5b8oXRV3lwkQSb/LAWylvDMae6RgEWWntd0SHtkYbQukDHKlfaYtVnSAgIavJ0kqszF/AIBb6g== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/hoek" "8.x.x" + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/catbox-memory@4.x.x": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@hapi/catbox-memory/-/catbox-memory-4.1.1.tgz#263a6f3361f7a200552c5772c98a8e80a1da712f" - integrity sha512-T6Hdy8DExzG0jY7C8yYWZB4XHfc0v+p1EGkwxl2HoaPYAmW7I3E59M/IvmSVpis8RPcIoBp41ZpO2aZPBpM2Ww== +"@hapi/catbox-memory@5.x.x": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@hapi/catbox-memory/-/catbox-memory-5.0.0.tgz#6c18dad1a80737480d1c33bfbefd5d028deec86d" + integrity sha512-ByuxVJPHNaXwLzbBv4GdTr6ccpe1nG+AfYt+8ftDWEJY7EWBWzD+Klhy5oPTDGzU26pNUh1e7fcYI1ILZRxAXQ== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/hoek" "8.x.x" + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/catbox@10.x.x": - version "10.2.3" - resolved "https://registry.yarnpkg.com/@hapi/catbox/-/catbox-10.2.3.tgz#2df51ab943d7613df3718fa2bfd981dd9558cec5" - integrity sha512-kN9hXO4NYyOHW09CXiuj5qW1syc/0XeVOBsNNk0Tz89wWNQE5h21WF+VsfAw3uFR8swn/Wj3YEVBnWqo82m/JQ== +"@hapi/catbox@^11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@hapi/catbox/-/catbox-11.1.1.tgz#d277e2d5023fd69cddb33d05b224ea03065fec0c" + integrity sha512-u/8HvB7dD/6X8hsZIpskSDo4yMKpHxFd7NluoylhGrL6cUfYxdQPnvUp9YU2C6F9hsyBVLGulBd9vBN1ebfXOQ== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/joi" "16.x.x" - "@hapi/podium" "3.x.x" + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/podium" "4.x.x" + "@hapi/validate" "1.x.x" -"@hapi/content@^4.1.1": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@hapi/content/-/content-4.1.1.tgz#179673d1e2b7eb36c564d8f9605d019bd2252cbf" - integrity sha512-3TWvmwpVPxFSF3KBjKZ8yDqIKKZZIm7VurDSweYpXYENZrJH3C1hd1+qEQW9wQaUaI76pPBLGrXl6I3B7i3ipA== +"@hapi/content@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@hapi/content/-/content-5.0.2.tgz#ae57954761de570392763e64cdd75f074176a804" + integrity sha512-mre4dl1ygd4ZyOH3tiYBrOUBzV7Pu/EOs8VLGf58vtOEECWed8Uuw6B4iR9AN/8uQt42tB04qpVaMyoMQh0oMw== dependencies: - "@hapi/boom" "7.x.x" + "@hapi/boom" "9.x.x" -"@hapi/cookie@^10.1.2": - version "10.1.2" - resolved "https://registry.yarnpkg.com/@hapi/cookie/-/cookie-10.1.2.tgz#9ea7d80f05d764faaf892b84e80c1bf13f5e3bf5" - integrity sha512-wch/uT5NgDEujmaLIqUoohbEP6PUr4ML2Z6zqheWHeHrSzXangPH4dveW+fiMsoPMW2S9ecAyUjCfkh4qRfxjg== +"@hapi/cookie@^11.0.2": + version "11.0.2" + resolved "https://registry.yarnpkg.com/@hapi/cookie/-/cookie-11.0.2.tgz#7169c060157a3541146b976e5f0ca9b3f7577d7f" + integrity sha512-LRpSuHC53urzml83c5eUHSPPt7YtK1CaaPZU9KmnhZlacVVojrWJzOUIcwOADDvCZjDxowCO3zPMaOqzEm9kgg== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/bounce" "1.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/joi" "16.x.x" + "@hapi/boom" "9.x.x" + "@hapi/bounce" "2.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/validate" "1.x.x" -"@hapi/cryptiles@4.x.x": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-4.2.1.tgz#ff0f18d79074659838caedbb911851313ad1ffbc" - integrity sha512-XoqgKsHK0l/VpqPs+tr6j6vE+VQ3+2bkF2stvttmc7xAOf1oSAwHcJ0tlp/6MxMysktt1IEY0Csy3khKaP9/uQ== +"@hapi/cryptiles@5.x.x": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-5.1.0.tgz#655de4cbbc052c947f696148c83b187fc2be8f43" + integrity sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA== dependencies: - "@hapi/boom" "7.x.x" - -"@hapi/file@1.x.x": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@hapi/file/-/file-1.0.0.tgz#c91c39fd04db8bed5af82d2e032e7a4e65555b38" - integrity sha512-Bsfp/+1Gyf70eGtnIgmScvrH8sSypO3TcK3Zf0QdHnzn/ACnAkI6KLtGACmNRPEzzIy+W7aJX5E+1fc9GwIABQ== + "@hapi/boom" "9.x.x" -"@hapi/formula@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-1.2.0.tgz#994649c7fea1a90b91a0a1e6d983523f680e10cd" - integrity sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA== +"@hapi/file@2.x.x": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/file/-/file-2.0.0.tgz#2ecda37d1ae9d3078a67c13b7da86e8c3237dfb9" + integrity sha512-WSrlgpvEqgPWkI18kkGELEZfXr0bYLtr16iIN4Krh9sRnzBZN6nnWxHFxtsnP684wueEySBbXPDg/WfA9xJdBQ== "@hapi/formula@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-2.0.0.tgz#edade0619ed58c8e4f164f233cda70211e787128" integrity sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A== -"@hapi/good-squeeze@5.2.1": - version "5.2.1" - resolved "https://registry.yarnpkg.com/@hapi/good-squeeze/-/good-squeeze-5.2.1.tgz#a7ed3f344c9602348af8f059beda663610ab8a4c" - integrity sha512-ZBiRgEDMtI5XowD0i4jgYD3wntN2JneY5EA1lUbSk9YoVIV9rWc77+6S0oqwfG0nj4xU/FjrXHvAahNEvRc6tg== +"@hapi/good-squeeze@6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@hapi/good-squeeze/-/good-squeeze-6.0.0.tgz#bb72d6869cd7398b615a6b7270f630dc4f76aebf" + integrity sha512-UgHAF9Lm8fJPzgf2HymtowOwNc1+IL+p08YTVR+XA4d8nmyE1t9x3RLA4riqldnOKHkVqGakJ1jGqUG7jk77Cg== dependencies: - "@hapi/hoek" "8.x.x" + "@hapi/hoek" "9.x.x" fast-safe-stringify "2.x.x" -"@hapi/h2o2@^8.3.2": - version "8.3.2" - resolved "https://registry.yarnpkg.com/@hapi/h2o2/-/h2o2-8.3.2.tgz#008a8f9ec3d9bba29077691aa9ec0ace93d4de80" - integrity sha512-2WkZq+QAkvYHWGqnUuG0stcVeGyv9T7bopBYnCJSUEuvBZlUf2BTX2JCVSKxsnTLOxCYwoC/aI4Rr0ZSRd2oVg== - dependencies: - "@hapi/boom" "7.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/joi" "16.x.x" - "@hapi/wreck" "15.x.x" - -"@hapi/hapi@^18.4.1": - version "18.4.1" - resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-18.4.1.tgz#023fbc131074b1cb2cd7f6766d65f4b0e92df788" - integrity sha512-9HjVGa0Z4Qv9jk9AVoUdJMQLA+KuZ+liKWyEEkVBx3e3H1F0JM6aGbPkY9jRfwsITBWGBU2iXazn65SFKSi/tg== - dependencies: - "@hapi/accept" "^3.2.4" - "@hapi/ammo" "^3.1.2" - "@hapi/boom" "7.x.x" - "@hapi/bounce" "1.x.x" - "@hapi/call" "^5.1.3" - "@hapi/catbox" "10.x.x" - "@hapi/catbox-memory" "4.x.x" - "@hapi/heavy" "6.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/joi" "15.x.x" - "@hapi/mimos" "4.x.x" - "@hapi/podium" "3.x.x" - "@hapi/shot" "4.x.x" - "@hapi/somever" "2.x.x" - "@hapi/statehood" "6.x.x" - "@hapi/subtext" "^6.1.3" - "@hapi/teamwork" "3.x.x" - "@hapi/topo" "3.x.x" - -"@hapi/heavy@6.x.x": - version "6.2.2" - resolved "https://registry.yarnpkg.com/@hapi/heavy/-/heavy-6.2.2.tgz#d42a282c62d5bb6332e497d8ce9ba52f1609f3e6" - integrity sha512-PY1dCCO6dsze7RlafIRhTaGeyTgVe49A/lSkxbhKGjQ7x46o/OFf7hLiRqTCDh3atcEKf6362EaB3+kTUbCsVA== +"@hapi/h2o2@^9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@hapi/h2o2/-/h2o2-9.0.2.tgz#e9f1dfe789257c80d6ee37ec9fe358f8c69f855a" + integrity sha512-V7RsmVyl7uyWeuEko4uaSZbFpBHKcSFSui6PXNRaRLJHFX+iPbqWmeH6m1pW/WJ8DuaCVJFKhluDCDI9l4+1cw== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/joi" "16.x.x" - -"@hapi/hoek@8.x.x", "@hapi/hoek@^8.2.4", "@hapi/hoek@^8.3.0", "@hapi/hoek@^8.3.1", "@hapi/hoek@^8.5.1": - version "8.5.1" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" - integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/validate" "1.x.x" + "@hapi/wreck" "17.x.x" + +"@hapi/hapi@^20.0.3": + version "20.0.3" + resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-20.0.3.tgz#e72cad460394e6d2c15f9c57abb5d3332dea27e3" + integrity sha512-aqJVHVjoY3phiZsgsGjDRG15CoUNIs1azScqLZDOCZUSKYGTbzPi+K0QP+RUjUJ0m8L9dRuTZ27c8HKxG3wEhA== + dependencies: + "@hapi/accept" "^5.0.1" + "@hapi/ammo" "^5.0.1" + "@hapi/boom" "9.x.x" + "@hapi/bounce" "2.x.x" + "@hapi/call" "8.x.x" + "@hapi/catbox" "^11.1.1" + "@hapi/catbox-memory" "5.x.x" + "@hapi/heavy" "^7.0.1" + "@hapi/hoek" "9.x.x" + "@hapi/mimos" "5.x.x" + "@hapi/podium" "^4.1.1" + "@hapi/shot" "^5.0.1" + "@hapi/somever" "3.x.x" + "@hapi/statehood" "^7.0.3" + "@hapi/subtext" "^7.0.3" + "@hapi/teamwork" "5.x.x" + "@hapi/topo" "5.x.x" + "@hapi/validate" "^1.1.0" + +"@hapi/heavy@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@hapi/heavy/-/heavy-7.0.1.tgz#73315ae33b6e7682a0906b7a11e8ca70e3045874" + integrity sha512-vJ/vzRQ13MtRzz6Qd4zRHWS3FaUc/5uivV2TIuExGTM9Qk+7Zzqj0e2G7EpE6KztO9SalTbiIkTh7qFKj/33cA== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/validate" "1.x.x" -"@hapi/hoek@9.x.x", "@hapi/hoek@^9.0.0": +"@hapi/hoek@9.x.x", "@hapi/hoek@^9.0.0", "@hapi/hoek@^9.0.4", "@hapi/hoek@^9.1.0": version "9.1.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.1.0.tgz#6c9eafc78c1529248f8f4d92b0799a712b6052c6" integrity sha512-i9YbZPN3QgfighY/1X1Pu118VUz2Fmmhd6b2n0/O8YVgGGfw0FbUYoA97k7FkpGJ+pLCFEDLUmAPPV4D1kpeFw== -"@hapi/inert@^5.2.2": - version "5.2.2" - resolved "https://registry.yarnpkg.com/@hapi/inert/-/inert-5.2.2.tgz#3ba4d93afc6d5b42e4bab19cd09556ddd49b5dac" - integrity sha512-8IaGfAEF8SwZtpdaTq0G3aDPG35ZTfWKjnMNniG2N3kE+qioMsBuImIGxna8TNQ+sYMXYK78aqmvzbQHno8qSQ== +"@hapi/inert@^6.0.3": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@hapi/inert/-/inert-6.0.3.tgz#57af5d912893fabcb57eb4b956f84f6cd8020fe1" + integrity sha512-Z6Pi0Wsn2pJex5CmBaq+Dky9q40LGzXLUIUFrYpDtReuMkmfy9UuUeYc4064jQ1Xe9uuw7kbwE6Fq6rqKAdjAg== dependencies: - "@hapi/ammo" "3.x.x" - "@hapi/boom" "7.x.x" - "@hapi/bounce" "1.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/joi" "16.x.x" - lru-cache "4.1.x" + "@hapi/ammo" "5.x.x" + "@hapi/boom" "9.x.x" + "@hapi/bounce" "2.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/validate" "1.x.x" + lru-cache "^6.0.0" -"@hapi/iron@*", "@hapi/iron@5.x.x", "@hapi/iron@^5.1.4": - version "5.1.4" - resolved "https://registry.yarnpkg.com/@hapi/iron/-/iron-5.1.4.tgz#7406f36847f798f52b92d1d97f855e27973832b7" - integrity sha512-+ElC+OCiwWLjlJBmm8ZEWjlfzTMQTdgPnU/TsoU5QsktspIWmWi9IU4kU83nH+X/SSya8TP8h8P11Wr5L7dkQQ== - dependencies: - "@hapi/b64" "4.x.x" - "@hapi/boom" "7.x.x" - "@hapi/bourne" "1.x.x" - "@hapi/cryptiles" "4.x.x" - "@hapi/hoek" "8.x.x" - -"@hapi/joi@15.x.x": - version "15.1.1" - resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" - integrity sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ== - dependencies: - "@hapi/address" "2.x.x" - "@hapi/bourne" "1.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/topo" "3.x.x" - -"@hapi/joi@16.x.x": - version "16.1.8" - resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-16.1.8.tgz#84c1f126269489871ad4e2decc786e0adef06839" - integrity sha512-wAsVvTPe+FwSrsAurNt5vkg3zo+TblvC5Bb1zMVK6SJzZqw9UrJnexxR+76cpePmtUZKHAPxcQ2Bf7oVHyahhg== - dependencies: - "@hapi/address" "^2.1.2" - "@hapi/formula" "^1.2.0" - "@hapi/hoek" "^8.2.4" - "@hapi/pinpoint" "^1.0.2" - "@hapi/topo" "^3.1.3" - -"@hapi/mimos@4.x.x": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@hapi/mimos/-/mimos-4.1.1.tgz#4dab8ed5c64df0603c204c725963a5faa4687e8a" - integrity sha512-CXoi/zfcTWfKYX756eEea8rXJRIb9sR4d7VwyAH9d3BkDyNgAesZxvqIdm55npQc6S9mU3FExinMAQVlIkz0eA== +"@hapi/iron@6.x.x", "@hapi/iron@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@hapi/iron/-/iron-6.0.0.tgz#ca3f9136cda655bdd6028de0045da0de3d14436f" + integrity sha512-zvGvWDufiTGpTJPG1Y/McN8UqWBu0k/xs/7l++HVU535NLHXsHhy54cfEMdW7EjwKfbBfM9Xy25FmTiobb7Hvw== dependencies: - "@hapi/hoek" "8.x.x" + "@hapi/b64" "5.x.x" + "@hapi/boom" "9.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/cryptiles" "5.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/mimos@5.x.x": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@hapi/mimos/-/mimos-5.0.0.tgz#245c6c98b1cc2c13395755c730321b913de074eb" + integrity sha512-EVS6wJYeE73InTlPWt+2e3Izn319iIvffDreci3qDNT+t3lA5ylJ0/SoTaID8e0TPNUkHUSsgJZXEmLHvoYzrA== + dependencies: + "@hapi/hoek" "9.x.x" mime-db "1.x.x" -"@hapi/nigel@3.x.x": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@hapi/nigel/-/nigel-3.1.1.tgz#84794021c9ee6e48e854fea9fb76e9f7e78c99ad" - integrity sha512-R9YWx4S8yu0gcCBrMUDCiEFm1SQT895dMlYoeNBp8I6YhF1BFF1iYPueKA2Kkp9BvyHdjmvrxCOns7GMmpl+Fw== +"@hapi/nigel@4.x.x": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@hapi/nigel/-/nigel-4.0.2.tgz#8f84ef4bca4fb03b2376463578f253b0b8e863c4" + integrity sha512-ht2KoEsDW22BxQOEkLEJaqfpoKPXxi7tvabXy7B/77eFtOyG5ZEstfZwxHQcqAiZhp58Ae5vkhEqI03kawkYNw== dependencies: - "@hapi/hoek" "8.x.x" - "@hapi/vise" "3.x.x" + "@hapi/hoek" "^9.0.4" + "@hapi/vise" "^4.0.0" "@hapi/oppsy@3.x.x": version "3.0.0" @@ -2012,97 +1976,86 @@ dependencies: "@hapi/hoek" "9.x.x" -"@hapi/pez@^4.1.2": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@hapi/pez/-/pez-4.1.2.tgz#14984d0c31fed348f10c962968a21d9761f55503" - integrity sha512-8zSdJ8cZrJLFldTgwjU9Fb1JebID+aBCrCsycgqKYe0OZtM2r3Yv3aAwW5z97VsZWCROC1Vx6Mdn4rujh5Ktcg== +"@hapi/pez@^5.0.1": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@hapi/pez/-/pez-5.0.3.tgz#b75446e6fef8cbb16816573ab7da1b0522e7a2a1" + integrity sha512-mpikYRJjtrbJgdDHG/H9ySqYqwJ+QU/D7FXsYciS9P7NYBXE2ayKDAy3H0ou6CohOCaxPuTV4SZ0D936+VomHA== dependencies: - "@hapi/b64" "4.x.x" - "@hapi/boom" "7.x.x" - "@hapi/content" "^4.1.1" - "@hapi/hoek" "8.x.x" - "@hapi/nigel" "3.x.x" - -"@hapi/pinpoint@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-1.0.2.tgz#025b7a36dbbf4d35bf1acd071c26b20ef41e0d13" - integrity sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ== + "@hapi/b64" "5.x.x" + "@hapi/boom" "9.x.x" + "@hapi/content" "^5.0.2" + "@hapi/hoek" "9.x.x" + "@hapi/nigel" "4.x.x" "@hapi/pinpoint@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.0.tgz#805b40d4dbec04fc116a73089494e00f073de8df" integrity sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw== -"@hapi/podium@3.x.x", "@hapi/podium@^3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@hapi/podium/-/podium-3.4.3.tgz#d28935870ae1372e2f983a7161e710c968a60de1" - integrity sha512-QJlnYLEYZWlKQ9fSOtuUcpANyoVGwT68GA9P0iQQCAetBK0fI+nbRBt58+aMixoifczWZUthuGkNjqKxgPh/CQ== +"@hapi/podium@4.x.x", "@hapi/podium@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@hapi/podium/-/podium-4.1.1.tgz#106e5849f2cb19b8767cc16007e0107f27c3c791" + integrity sha512-jh7a6+5Z4FUWzx8fgmxjaAa1DTBu+Qfg+NbVdo0f++rE5DgsVidUYrLDp3db65+QjDLleA2MfKQXkpT8ylBDXA== dependencies: - "@hapi/hoek" "8.x.x" - "@hapi/joi" "16.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/teamwork" "5.x.x" + "@hapi/validate" "1.x.x" -"@hapi/shot@4.x.x": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@hapi/shot/-/shot-4.1.2.tgz#69f999956041fe468701a89a413175a521dabed5" - integrity sha512-6LeHLjvsq/bQ0R+fhEyr7mqExRGguNTrxFZf5DyKe3CK6pNabiGgYO4JVFaRrLZ3JyuhkS0fo8iiRE2Ql2oA/A== +"@hapi/shot@^5.0.1": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@hapi/shot/-/shot-5.0.4.tgz#6c978314f21a054c041f4becc50095dd78d3d775" + integrity sha512-PcEz0WJgFDA3xNSMeONgQmothFr7jhbbRRSAKaDh7chN7zOXBlhl13bvKZW6CMb2xVfJUmt34CW3e/oExMgBhQ== dependencies: - "@hapi/hoek" "8.x.x" - "@hapi/joi" "16.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/validate" "1.x.x" -"@hapi/somever@2.x.x": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@hapi/somever/-/somever-2.1.1.tgz#142bddf7cc4d829f678ed4e60618630a9a7ae845" - integrity sha512-cic5Sto4KGd9B0oQSdKTokju+rYhCbdpzbMb0EBnrH5Oc1z048hY8PaZ1lx2vBD7I/XIfTQVQetBH57fU51XRA== +"@hapi/somever@3.x.x": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@hapi/somever/-/somever-3.0.0.tgz#f4e9b16a948415b926b4dd898013602b0cb45758" + integrity sha512-Upw/kmKotC9iEmK4y047HMYe4LDKsE5NWfjgX41XNKmFvxsQL7OiaCWVhuyyhU0ShDGBfIAnCH8jZr49z/JzZA== dependencies: - "@hapi/bounce" "1.x.x" - "@hapi/hoek" "8.x.x" + "@hapi/bounce" "2.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/statehood@6.x.x", "@hapi/statehood@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@hapi/statehood/-/statehood-6.1.2.tgz#6dda508b5da99a28a3ed295c3cac795cf6c12a02" - integrity sha512-pYXw1x6npz/UfmtcpUhuMvdK5kuOGTKcJNfLqdNptzietK2UZH5RzNJSlv5bDHeSmordFM3kGItcuQWX2lj2nQ== - dependencies: - "@hapi/boom" "7.x.x" - "@hapi/bounce" "1.x.x" - "@hapi/bourne" "1.x.x" - "@hapi/cryptiles" "4.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/iron" "5.x.x" - "@hapi/joi" "16.x.x" - -"@hapi/subtext@^6.1.3": - version "6.1.3" - resolved "https://registry.yarnpkg.com/@hapi/subtext/-/subtext-6.1.3.tgz#bbd07771ae2a4e73ac360c93ed74ac641718b9c6" - integrity sha512-qWN6NbiHNzohVcJMeAlpku/vzbyH4zIpnnMPMPioQMwIxbPFKeNViDCNI6fVBbMPBiw/xB4FjqiJkRG5P9eWWg== - dependencies: - "@hapi/boom" "7.x.x" - "@hapi/bourne" "1.x.x" - "@hapi/content" "^4.1.1" - "@hapi/file" "1.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/pez" "^4.1.2" - "@hapi/wreck" "15.x.x" - -"@hapi/teamwork@3.x.x": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-3.3.1.tgz#b52d0ec48682dc793926bd432e22ceb19c915d3f" - integrity sha512-61tiqWCYvMKP7fCTXy0M4VE6uNIwA0qvgFoiDubgfj7uqJ0fdHJFQNnVPGrxhLWlwz0uBPWrQlBH7r8y9vFITQ== +"@hapi/statehood@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@hapi/statehood/-/statehood-7.0.3.tgz#655166f3768344ed3c3b50375a303cdeca8040d9" + integrity sha512-pYB+pyCHkf2Amh67QAXz7e/DN9jcMplIL7Z6N8h0K+ZTy0b404JKPEYkbWHSnDtxLjJB/OtgElxocr2fMH4G7w== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/bounce" "2.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/cryptiles" "5.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/iron" "6.x.x" + "@hapi/validate" "1.x.x" -"@hapi/topo@3.x.x", "@hapi/topo@^3.1.3": - version "3.1.6" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.6.tgz#68d935fa3eae7fdd5ab0d7f953f3205d8b2bfc29" - integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ== +"@hapi/subtext@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@hapi/subtext/-/subtext-7.0.3.tgz#f7440fc7c966858e1f39681e99eb6171c71e7abd" + integrity sha512-CekDizZkDGERJ01C0+TzHlKtqdXZxzSWTOaH6THBrbOHnsr3GY+yiMZC+AfNCypfE17RaIakGIAbpL2Tk1z2+A== dependencies: - "@hapi/hoek" "^8.3.0" + "@hapi/boom" "9.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/content" "^5.0.2" + "@hapi/file" "2.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/pez" "^5.0.1" + "@hapi/wreck" "17.x.x" -"@hapi/topo@^5.0.0": +"@hapi/teamwork@5.x.x": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-5.1.0.tgz#7801a61fc727f702fd2196ef7625eb4e389f4124" + integrity sha512-llqoQTrAJDTXxG3c4Kz/uzhBS1TsmSBa/XG5SPcVXgmffHE1nFtyLIK0hNJHCB3EuBKT84adzd1hZNY9GJLWtg== + +"@hapi/topo@5.x.x", "@hapi/topo@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.0.0.tgz#c19af8577fa393a06e9c77b60995af959be721e7" integrity sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw== dependencies: "@hapi/hoek" "^9.0.0" -"@hapi/validate@1.x.x": +"@hapi/validate@1.x.x", "@hapi/validate@^1.1.0": version "1.1.3" resolved "https://registry.yarnpkg.com/@hapi/validate/-/validate-1.1.3.tgz#f750a07283929e09b51aa16be34affb44e1931ad" integrity sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA== @@ -2110,31 +2063,31 @@ "@hapi/hoek" "^9.0.0" "@hapi/topo" "^5.0.0" -"@hapi/vise@3.x.x": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@hapi/vise/-/vise-3.1.1.tgz#dfc88f2ac90682f48bdc1b3f9b8f1eab4eabe0c8" - integrity sha512-OXarbiCSadvtg+bSdVPqu31Z1JoBL+FwNYz3cYoBKQ5xq1/Cr4A3IkGpAZbAuxU5y4NL5pZFZG3d2a3ZGm/dOQ== +"@hapi/vise@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@hapi/vise/-/vise-4.0.0.tgz#c6a94fe121b94a53bf99e7489f7fcc74c104db02" + integrity sha512-eYyLkuUiFZTer59h+SGy7hUm+qE9p+UemePTHLlIWppEd+wExn3Df5jO04bFQTm7nleF5V8CtuYQYb+VFpZ6Sg== dependencies: - "@hapi/hoek" "8.x.x" + "@hapi/hoek" "9.x.x" -"@hapi/vision@^5.5.4": - version "5.5.4" - resolved "https://registry.yarnpkg.com/@hapi/vision/-/vision-5.5.4.tgz#03a01374fb5e0a498d6e502e635a0b54d70501a1" - integrity sha512-/DFgnQtcrlf2eQNkh/DHnjrCRHLSmHraU+PHe1SlxLUJxATQCw8VIEt6rJraM2jGTpFgHNk6B6ELtu3sBJCClg== +"@hapi/vision@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@hapi/vision/-/vision-6.0.1.tgz#976c3575be56d3cb5b472ddcfe0b7403778706fd" + integrity sha512-xv4PwmhbXCLzDfojZ7l4+P/YynBhMInV8GtLPH4gB74prhwOl8lGcJxxK8V9rf1aMH/vonM5yVGd9FuoA9sT0A== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/bounce" "1.x.x" - "@hapi/hoek" "8.x.x" - "@hapi/joi" "16.x.x" + "@hapi/boom" "9.x.x" + "@hapi/bounce" "2.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/validate" "1.x.x" -"@hapi/wreck@15.x.x", "@hapi/wreck@^15.0.2": - version "15.1.0" - resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-15.1.0.tgz#7917cd25950ce9b023f7fd2bea6e2ef72c71e59d" - integrity sha512-tQczYRTTeYBmvhsek/D49En/5khcShaBEmzrAaDjMrFXKJRuF8xA8+tlq1ETLBFwGd6Do6g2OC74rt11kzawzg== +"@hapi/wreck@17.x.x", "@hapi/wreck@^17.0.0", "@hapi/wreck@^17.1.0": + version "17.1.0" + resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-17.1.0.tgz#fbdc380c6f3fa1f8052dc612b2d3b6ce3e88dbec" + integrity sha512-nx6sFyfqOpJ+EFrHX+XWwJAxs3ju4iHdbB/bwR8yTNZOiYmuhA8eCe7lYPtYmb4j7vyK/SlbaQsmTtUrMvPEBw== dependencies: - "@hapi/boom" "7.x.x" - "@hapi/bourne" "1.x.x" - "@hapi/hoek" "8.x.x" + "@hapi/boom" "9.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/hoek" "9.x.x" "@icons/material@^0.2.4": version "0.2.4" @@ -2998,7 +2951,7 @@ resolved "https://registry.yarnpkg.com/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz#15651bd553a67b8581fb398810c98ad86a34524e" integrity sha1-FWUb1VOme4WB+zmIEMmK2Go0Uk4= -"@mapbox/vector-tile@^1.3.1": +"@mapbox/vector-tile@1.3.1", "@mapbox/vector-tile@^1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz#d3a74c90402d06e89ec66de49ec817ff53409666" integrity sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw== @@ -4869,11 +4822,6 @@ "@types/vinyl-fs" "*" chokidar "^2.1.2" -"@types/hapi__boom@*", "@types/hapi__boom@^7.4.1": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@types/hapi__boom/-/hapi__boom-7.4.1.tgz#06439d7637245dcbe6dd6548d2a91f2c1243d80b" - integrity sha512-x/ZK824GomII7Yoei/nMoB46NQcSfGe0iVpZK3uUivxIAfUUSzRvu8RQO7ZkKapIgzgshHZc+GR+z/BQ8l2VLg== - "@types/hapi__catbox@*": version "10.2.3" resolved "https://registry.yarnpkg.com/@types/hapi__catbox/-/hapi__catbox-10.2.3.tgz#c9279c16d709bf2987491c332e11d18124ae018f" @@ -4886,54 +4834,38 @@ dependencies: "@types/hapi__hapi" "*" -"@types/hapi__h2o2@8.3.0": - version "8.3.0" - resolved "https://registry.yarnpkg.com/@types/hapi__h2o2/-/hapi__h2o2-8.3.0.tgz#c2e6598ab6ed28edb1a5edd44ddc185e1c252dd8" - integrity sha512-jD6L+8BJ+SVbwBzQK3W7zGnDYgrwuCNDl9r1P0GdwoYsysNADl7STfrhJ/m9qPt2fD1vFVJsfsFjoJ/iCyNlOQ== +"@types/hapi__h2o2@^8.3.2": + version "8.3.2" + resolved "https://registry.yarnpkg.com/@types/hapi__h2o2/-/hapi__h2o2-8.3.2.tgz#43cce95972c3097a2ca3efe6b7054a0c95fbf291" + integrity sha512-l36uuLHTwUQNbNUIkT14Z4WbJl1CIWpBZu7ZCBemGBypiNnbJxN3o0YyQ6QAid3YYa2C7LVDIdyY4MhpX8q9ZA== dependencies: - "@types/hapi__boom" "*" + "@hapi/boom" "^9.0.0" + "@hapi/wreck" "^17.0.0" "@types/hapi__hapi" "*" "@types/node" "*" -"@types/hapi__hapi@*", "@types/hapi__hapi@^18.2.6": - version "18.2.6" - resolved "https://registry.yarnpkg.com/@types/hapi__hapi/-/hapi__hapi-18.2.6.tgz#61c1b210c55dee4636df594e7a0868ad48c8042a" - integrity sha512-sXFlSg9btu/LdHqK/N/kuQXVqZhSvibXbtZc0KfEcelRXKthdU5ZSu5qItDIov7SYnyK2faMe7dbZaC/VpC33w== +"@types/hapi__hapi@*", "@types/hapi__hapi@^20.0.2": + version "20.0.2" + resolved "https://registry.yarnpkg.com/@types/hapi__hapi/-/hapi__hapi-20.0.2.tgz#e7571451f7fb75e87ab3873ec91b92f92cd55fff" + integrity sha512-7FwFoaxSCtHXbHbDdArSeVABFOfMLgVkOvOUtWrqUBzw639B2rq9OHv3kOVDHY0bOao0f6ubMzUxio8WQ9QZfQ== dependencies: - "@types/hapi__boom" "*" + "@hapi/boom" "^9.0.0" + "@hapi/iron" "^6.0.0" "@types/hapi__catbox" "*" - "@types/hapi__iron" "*" - "@types/hapi__joi" "*" "@types/hapi__mimos" "*" "@types/hapi__podium" "*" "@types/hapi__shot" "*" + "@types/joi" "*" "@types/node" "*" -"@types/hapi__hoek@^6.2.0": - version "6.2.0" - resolved "https://registry.yarnpkg.com/@types/hapi__hoek/-/hapi__hoek-6.2.0.tgz#61ec4dfb93e6aaccf2b407d6074a0633069e5d2d" - integrity sha512-MMS8ZD0SR2lklVkpNJw7iUYBmlvBLw1T04VSBhbWpiOi0ee6RoJUCcocVao1FLnSYR8Tt03dykRBv+FkvPIJSg== - -"@types/hapi__inert@^5.2.1": - version "5.2.1" - resolved "https://registry.yarnpkg.com/@types/hapi__inert/-/hapi__inert-5.2.1.tgz#cce395e7470a969f63cf57d561da230218b8b2bb" - integrity sha512-pFvXfN9bTGgR6jkgKseXmu5/eHVGVEsGh0LKHCkcezEqZZMJV9YabREVLa6kcYEQMIDQzQSwSakSnemCFiSnOg== +"@types/hapi__inert@^5.2.2": + version "5.2.2" + resolved "https://registry.yarnpkg.com/@types/hapi__inert/-/hapi__inert-5.2.2.tgz#6513c487d216ed9377c2c0efceb178fda0928bfa" + integrity sha512-Vp9HS2wi3Qbm1oUlcTvzA2Zd+f3Dwg+tgLqWA6KTCgKbQX4LCPKIvVssbaQAVncmcpH0aPrtkAfftJlS/sMsGg== dependencies: "@types/hapi__hapi" "*" -"@types/hapi__iron@*": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@types/hapi__iron/-/hapi__iron-6.0.1.tgz#ec8b23eff3d69313f1187c234deb80652384ad6b" - integrity sha512-NTr+1FKl+nvEeSwVpfcks36dCm6+tbcQh3tJYbyQ5XWb5sIbCIptW6p38zmCYE5ppOoU/2PK1Y8taGpl6cOl5w== - dependencies: - "@hapi/iron" "*" - -"@types/hapi__joi@*": - version "17.1.6" - resolved "https://registry.yarnpkg.com/@types/hapi__joi/-/hapi__joi-17.1.6.tgz#b84663676aa9753c17183718338dd40ddcbd3754" - integrity sha512-y3A1MzNC0FmzD5+ys59RziE1WqKrL13nxtJgrSzjoO7boue5B7zZD2nZLPwrSuUviFjpKFQtgHYSvhDGfIE4jA== - -"@types/hapi__mimos@*", "@types/hapi__mimos@4.1.0": +"@types/hapi__mimos@*": version "4.1.0" resolved "https://registry.yarnpkg.com/@types/hapi__mimos/-/hapi__mimos-4.1.0.tgz#47dbf89ebfc05183c1de2797e9426793db9a0d85" integrity sha512-hcdSoYa32wcP+sEfyf85ieGwElwokcZ/mma8eyqQ4OTHeCAGwfaoiGxjG4z1Dm+RGhIYLHlW54ji5FFwahH12A== @@ -4952,14 +4884,6 @@ dependencies: "@types/node" "*" -"@types/hapi__wreck@^15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@types/hapi__wreck/-/hapi__wreck-15.0.1.tgz#41df4e122c49316f0057cb5e9c6eb4c00e671e95" - integrity sha512-OXhOaFWPFkWkqU5IlFwgTK/Q3yzc3iDhC1/S+3rQ6d2qkl6xvcRZaayJGjDXORf3krnGtDN1l3bIajNcuUl6QA== - dependencies: - "@types/hapi__boom" "*" - "@types/node" "*" - "@types/has-ansi@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/has-ansi/-/has-ansi-3.0.0.tgz#636403dc4e0b2649421c4158e5c404416f3f0330" @@ -5099,6 +5023,11 @@ jest-diff "^25.2.1" pretty-format "^25.2.1" +"@types/joi@*": + version "14.3.4" + resolved "https://registry.yarnpkg.com/@types/joi/-/joi-14.3.4.tgz#eed1e14cbb07716079c814138831a520a725a1e0" + integrity sha512-1TQNDJvIKlgYXGNIABfgFp9y0FziDpuGrd799Q5RcnsDu+krD+eeW/0Fs5PHARvWWFelOhIG2OPCo6KbadBM4A== + "@types/joi@^13.4.2": version "13.6.1" resolved "https://registry.yarnpkg.com/@types/joi/-/joi-13.6.1.tgz#325486a397504f8e22c8c551dc8b0e1d41d5d5ae" @@ -19303,7 +19232,7 @@ lowlight@^1.14.0, lowlight@^1.2.0: fault "^1.0.0" highlight.js "~10.4.0" -lru-cache@4.1.x, lru-cache@^4.0.0, lru-cache@^4.0.1, lru-cache@^4.1.5: +lru-cache@^4.0.0, lru-cache@^4.0.1, lru-cache@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== @@ -21931,7 +21860,7 @@ pathval@^1.1.0: resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= -pbf@^3.0.5, pbf@^3.2.1: +pbf@3.2.1, pbf@^3.0.5, pbf@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/pbf/-/pbf-3.2.1.tgz#b4c1b9e72af966cd82c6531691115cc0409ffe2a" integrity sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==