diff --git a/.ci/Dockerfile b/.ci/Dockerfile index ec7befe05f0d4..8a972c65f8412 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.1 +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 adb884ca064ba..fc57811bb2077 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 3f7daef6d0473..751ec5a03ee7b 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 0000000000000..387ec0c126785 --- /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 0000000000000..f2a4a20568215 --- /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 0000000000000..6703ffffb5651 --- /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 0000000000000..5acc4b2ae351b --- /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 5d1ecbcbd48ee..3570bf01e49c4 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 f662d36247a2f..982d129dae2a6 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 420568d75691b..19c4c189d3640 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -14.15.2 +14.15.3 diff --git a/.nvmrc b/.nvmrc index 420568d75691b..19c4c189d3640 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -14.15.2 +14.15.3 diff --git a/.teamcity/pom.xml b/.teamcity/pom.xml index 5fa068d0a92e0..e6ec1f1c043c2 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 ec1b1c6eb94ef..28108d019327b 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 0000000000000..557cce80d0f55 --- /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 120b333d43e72..2942a6385f13f 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 1228ea4d94f4c..37336316c4c91 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 7dbe9cd0ba84c..2c3b0d348591e 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 6f1d45598c92e..4f39283149e73 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 c778814af536c..4c0307e9eca55 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 ca58b628cbd22..a04512fb2aba5 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 d1b5898d1a5f5..9506d98cbe50e 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 5b1d2541480ad..a294fce9599c3 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 20c30eedf5b91..1878f49debe8c 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 762218b72ab10..1f7f364600e21 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 117c30ddb86e3..83fe4fdaa1edd 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 677effec5be65..311c15a1da7cb 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/data/.empty b/data/.empty deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/docs/api/upgrade-assistant.asciidoc b/docs/api/upgrade-assistant.asciidoc index 15d87fbd0dc9d..39d3cb9ecb76b 100644 --- a/docs/api/upgrade-assistant.asciidoc +++ b/docs/api/upgrade-assistant.asciidoc @@ -22,5 +22,6 @@ include::upgrade-assistant/status.asciidoc[] include::upgrade-assistant/reindexing.asciidoc[] include::upgrade-assistant/batch_reindexing.asciidoc[] include::upgrade-assistant/batch_queue.asciidoc[] +include::upgrade-assistant/default-field.asciidoc[] include::upgrade-assistant/check_reindex_status.asciidoc[] include::upgrade-assistant/cancel_reindex.asciidoc[] diff --git a/docs/api/upgrade-assistant/check_reindex_status.asciidoc b/docs/api/upgrade-assistant/check_reindex_status.asciidoc index 98cf263673f73..75aac7b3699f5 100644 --- a/docs/api/upgrade-assistant/check_reindex_status.asciidoc +++ b/docs/api/upgrade-assistant/check_reindex_status.asciidoc @@ -6,8 +6,6 @@ experimental[] Check the status of the reindex operation. -Check the status of the reindex operation. - [[check-reindex-status-request]] ==== Request diff --git a/docs/api/upgrade-assistant/default-field.asciidoc b/docs/api/upgrade-assistant/default-field.asciidoc new file mode 100644 index 0000000000000..8bdcd359d5668 --- /dev/null +++ b/docs/api/upgrade-assistant/default-field.asciidoc @@ -0,0 +1,113 @@ +[[upgrade-assistant-api-default-field]] +=== Add default field API +++++ +Add default field +++++ + +experimental[] In {es} 7.0 and later, some query types, such as Simple Query String, have a limit to the number of fields they can query against. +To configure the cap in {es}, set the `indices.query.bool.max_clause_count` cluster setting, which is 1024 by default. + +For indices with more fields than the cap, add the `index.query.default_field` index setting to inform {es} which +fields to use by default when no field is specified for a query. Use the add default field API to add the `index.query.default_field` setting to an {es} index. + +[[upgrade-assistant-api-default-field-request]] +==== Request + +To add the `index.query.default_field` setting to an {es} index, submit a POST request to `/api/upgrade_assistant/add_query_default_field/`: + +[source,js] +-------------------------------------------------- +GET /api/upgrade_assistant/add_query_default_field/myIndex +{ + "fieldTypes": ["text", "keyword"], <1> + "otherFields": ["myField.*"] <2> +} +-------------------------------------------------- +// KIBANA + +<1> A required array of {es} field types that generate the list of fields. +<2> An optional array of additional field names, dot-deliminated. + +To add the `index.query.default_field` index setting to the specified index, {kib} generates an array of all fields from the index mapping. +The fields contain the types specified in `fieldTypes`. {kib} appends any other fields specified in `otherFields` to the array of default fields. + +[[upgrade-assistant-api-default-field-response-codes]] +==== Response codes + +`200`:: + Indicates a successful call. + + `400`:: + Indicates that the index already has the `index.query.default_field` setting. No changes are made to the index. + +[[upgrade-assistant-api-default-field-response-body]] +==== Response body + +The response body contains a JSON structure, similar to the following: + +[source,js] +-------------------------------------------------- +{ + "acknowledged": true +} +-------------------------------------------------- + +[[upgrade-assistant-api-default-field-example]] +==== Example + +Your index contains following mappings: + +[source,js] +-------------------------------------------------- +GET /myIndex/_mappings +{ + "myIndex": { + "mappings": { + "properties": { + "field1": { "type": "text" }, + "field2": { "type": "float" }, + "nestedfield": { + "properties": { + "field3": { "type": "keyword" }, + "field4": { "type": "long" }, + } + } + } + } + } +} +-------------------------------------------------- +// CONSOLE + +Make the following request to {kib}: + +[source,js] +-------------------------------------------------- +GET /api/upgrade_assistant/add_query_default_field/myIndex +{ + "fieldTypes": ["text", "long"], + "otherFields": ["field2"] +} +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,js] +-------------------------------------------------- +GET /myIndex/_settings?flat_settings=true +{ + "myIndex": { + "settings": { + "index.query.default_field": [ + "field1", + "nestedfield.field4", + "field2", + ] + } + } +} +-------------------------------------------------- +// CONSOLE + +{kib} generates the `field1` and `nestedfield.field4` values based on the specified `fieldTypes`, then appends the `otherFields` to the array. \ No newline at end of file diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc index f49e2a944c900..0df8357bb3bd6 100644 --- a/docs/canvas/canvas-share-workpad.asciidoc +++ b/docs/canvas/canvas-share-workpad.asciidoc @@ -36,7 +36,7 @@ For more information, refer to <> or a script. -To begin, click *Share > PDF reports > Copy POST URL*. +To begin, click *Share > PDF reports > Advanced options > Copy POST URL*. [role="screenshot"] image::images/canvas-create-URL.gif[Image showing how to create POST URL] diff --git a/docs/canvas/images/canvas-create-URL.gif b/docs/canvas/images/canvas-create-URL.gif index 11327224fc897..60d69cdd599a3 100644 Binary files a/docs/canvas/images/canvas-create-URL.gif and b/docs/canvas/images/canvas-create-URL.gif differ diff --git a/docs/canvas/images/canvas-generate-pdf.gif b/docs/canvas/images/canvas-generate-pdf.gif index 513f6b3b960f9..24711d01fbe0c 100644 Binary files a/docs/canvas/images/canvas-generate-pdf.gif and b/docs/canvas/images/canvas-generate-pdf.gif differ diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index e32984f911d97..d4d2b229eeba7 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -272,7 +272,7 @@ heatmap charts. |{kib-repo}blob/{branch}/src/plugins/vis_type_xy/README.md[visTypeXy] |Contains the new xy-axis chart using the elastic-charts library, which will eventually -replace the vislib xy-axis (bar, area, line) charts. +replace the vislib xy-axis charts including bar, area, and line. |{kib-repo}blob/{branch}/src/plugins/visualizations/README.md[visualizations] diff --git a/docs/development/core/public/kibana-plugin-core-public.app.md b/docs/development/core/public/kibana-plugin-core-public.app.md index 8e8bae5ad9c58..b24ced68b7d38 100644 --- a/docs/development/core/public/kibana-plugin-core-public.app.md +++ b/docs/development/core/public/kibana-plugin-core-public.app.md @@ -24,10 +24,10 @@ export interface App | [exactRoute](./kibana-plugin-core-public.app.exactroute.md) | boolean | If set to true, the application's route will only be checked against an exact match. Defaults to false. | | [icon](./kibana-plugin-core-public.app.icon.md) | string | A URL to an image file used as an icon. Used as a fallback if euiIconType is not provided. | | [id](./kibana-plugin-core-public.app.id.md) | string | The unique identifier of the application | +| [meta](./kibana-plugin-core-public.app.meta.md) | AppMeta | Meta data for an application that represent additional information for the app. See [AppMeta](./kibana-plugin-core-public.appmeta.md) | | [mount](./kibana-plugin-core-public.app.mount.md) | AppMount<HistoryLocationState> | AppMountDeprecated<HistoryLocationState> | A mount function called when the user navigates to this app's route. May have signature of [AppMount](./kibana-plugin-core-public.appmount.md) or [AppMountDeprecated](./kibana-plugin-core-public.appmountdeprecated.md). | | [navLinkStatus](./kibana-plugin-core-public.app.navlinkstatus.md) | AppNavLinkStatus | The initial status of the application's navLink. Defaulting to visible if status is accessible and hidden if status is inaccessible See [AppNavLinkStatus](./kibana-plugin-core-public.appnavlinkstatus.md) | | [order](./kibana-plugin-core-public.app.order.md) | number | An ordinal used to sort nav links relative to one another for display. | -| [searchDeepLinks](./kibana-plugin-core-public.app.searchdeeplinks.md) | AppSearchDeepLink[] | Array of links that represent secondary in-app locations for the app. | | [status](./kibana-plugin-core-public.app.status.md) | AppStatus | The initial status of the application. Defaulting to accessible | | [title](./kibana-plugin-core-public.app.title.md) | string | The title of the application. | | [tooltip](./kibana-plugin-core-public.app.tooltip.md) | string | A tooltip shown when hovering over app link. | diff --git a/docs/development/core/public/kibana-plugin-core-public.app.meta.md b/docs/development/core/public/kibana-plugin-core-public.app.meta.md new file mode 100644 index 0000000000000..574fa11605aec --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.meta.md @@ -0,0 +1,43 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [meta](./kibana-plugin-core-public.app.meta.md) + +## App.meta property + +Meta data for an application that represent additional information for the app. See [AppMeta](./kibana-plugin-core-public.appmeta.md) + +Signature: + +```typescript +meta?: AppMeta; +``` + +## Remarks + +Used to populate navigational search results (where available). Can be updated using the [App.updater$](./kibana-plugin-core-public.app.updater_.md) observable. See [PublicAppSearchDeepLinkInfo](./kibana-plugin-core-public.publicappsearchdeeplinkinfo.md) for more details. + +## Example + + +```ts +core.application.register({ + id: 'my_app', + title: 'Translated title', + meta: { + keywords: ['translated keyword1', 'translated keyword2'], + searchDeepLinks: [ + { id: 'sub1', title: 'Sub1', path: '/sub1', keywords: ['subpath1'] }, + { + id: 'sub2', + title: 'Sub2', + searchDeepLinks: [ + { id: 'subsub', title: 'SubSub', path: '/sub2/sub', keywords: ['subpath2'] } + ] + } + ], + }, + mount: () => { ... } +}) + +``` + diff --git a/docs/development/core/public/kibana-plugin-core-public.app.searchdeeplinks.md b/docs/development/core/public/kibana-plugin-core-public.app.searchdeeplinks.md deleted file mode 100644 index 667fddbc212a5..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.app.searchdeeplinks.md +++ /dev/null @@ -1,42 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [searchDeepLinks](./kibana-plugin-core-public.app.searchdeeplinks.md) - -## App.searchDeepLinks property - -Array of links that represent secondary in-app locations for the app. - -Signature: - -```typescript -searchDeepLinks?: AppSearchDeepLink[]; -``` - -## Remarks - -Used to populate navigational search results (where available). Can be updated using the [App.updater$](./kibana-plugin-core-public.app.updater_.md) observable. See for more details. - -## Example - -The `path` property on deep links should not include the application's `appRoute`: - -```ts -core.application.register({ - id: 'my_app', - title: 'My App', - searchDeepLinks: [ - { id: 'sub1', title: 'Sub1', path: '/sub1' }, - { - id: 'sub2', - title: 'Sub2', - searchDeepLinks: [ - { id: 'subsub', title: 'SubSub', path: '/sub2/sub' } - ] - } - ], - mount: () => { ... }, -}) - -``` -Will produce deep links on these paths: - `/app/my_app/sub1` - `/app/my_app/sub2/sub` - diff --git a/docs/development/core/public/kibana-plugin-core-public.appmeta.keywords.md b/docs/development/core/public/kibana-plugin-core-public.appmeta.keywords.md new file mode 100644 index 0000000000000..13709df68e76a --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.appmeta.keywords.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppMeta](./kibana-plugin-core-public.appmeta.md) > [keywords](./kibana-plugin-core-public.appmeta.keywords.md) + +## AppMeta.keywords property + +Keywords to represent this application + +Signature: + +```typescript +keywords?: string[]; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appmeta.md b/docs/development/core/public/kibana-plugin-core-public.appmeta.md new file mode 100644 index 0000000000000..a2b72f7ec799d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.appmeta.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppMeta](./kibana-plugin-core-public.appmeta.md) + +## AppMeta interface + +Input type for meta data for an application. + +Meta fields include `keywords` and `searchDeepLinks` Keywords is an array of string with which to associate the app, must include at least one unique string as an array. `searchDeepLinks` is an array of links that represent secondary in-app locations for the app. + +Signature: + +```typescript +export interface AppMeta +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [keywords](./kibana-plugin-core-public.appmeta.keywords.md) | string[] | Keywords to represent this application | +| [searchDeepLinks](./kibana-plugin-core-public.appmeta.searchdeeplinks.md) | AppSearchDeepLink[] | Array of links that represent secondary in-app locations for the app. | + diff --git a/docs/development/core/public/kibana-plugin-core-public.appmeta.searchdeeplinks.md b/docs/development/core/public/kibana-plugin-core-public.appmeta.searchdeeplinks.md new file mode 100644 index 0000000000000..7ec0bbaa4b418 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.appmeta.searchdeeplinks.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppMeta](./kibana-plugin-core-public.appmeta.md) > [searchDeepLinks](./kibana-plugin-core-public.appmeta.searchdeeplinks.md) + +## AppMeta.searchDeepLinks property + +Array of links that represent secondary in-app locations for the app. + +Signature: + +```typescript +searchDeepLinks?: AppSearchDeepLink[]; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appsearchdeeplink.md b/docs/development/core/public/kibana-plugin-core-public.appsearchdeeplink.md index 7e5ccf7d06ed1..29aad675fb105 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appsearchdeeplink.md +++ b/docs/development/core/public/kibana-plugin-core-public.appsearchdeeplink.md @@ -17,8 +17,10 @@ export declare type AppSearchDeepLink = { } & ({ path: string; searchDeepLinks?: AppSearchDeepLink[]; + keywords?: string[]; } | { path?: string; searchDeepLinks: AppSearchDeepLink[]; + keywords?: string[]; }); ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md b/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md index b6f404c3d11aa..55672d9339f61 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md +++ b/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md @@ -9,5 +9,5 @@ Defines the list of fields that can be updated via an [AppUpdater](./kibana-plug Signature: ```typescript -export declare type AppUpdatableFields = Pick; +export declare type AppUpdatableFields = Pick; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index da19377054499..7f671d9edcd86 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -37,6 +37,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [AppLeaveDefaultAction](./kibana-plugin-core-public.appleavedefaultaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-core-public.appleavehandler.md) to execute the default behaviour when leaving the application.See | | [ApplicationSetup](./kibana-plugin-core-public.applicationsetup.md) | | | [ApplicationStart](./kibana-plugin-core-public.applicationstart.md) | | +| [AppMeta](./kibana-plugin-core-public.appmeta.md) | Input type for meta data for an application.Meta fields include keywords and searchDeepLinks Keywords is an array of string with which to associate the app, must include at least one unique string as an array. searchDeepLinks is an array of links that represent secondary in-app locations for the app. | | [AppMountContext](./kibana-plugin-core-public.appmountcontext.md) | The context object received when applications are mounted to the DOM. Deprecated, use [CoreSetup.getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md). | | [AppMountParameters](./kibana-plugin-core-public.appmountparameters.md) | | | [Capabilities](./kibana-plugin-core-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | @@ -164,6 +165,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginInitializer](./kibana-plugin-core-public.plugininitializer.md) | The plugin export at the root of a plugin's public directory should conform to this interface. | | [PluginOpaqueId](./kibana-plugin-core-public.pluginopaqueid.md) | | | [PublicAppInfo](./kibana-plugin-core-public.publicappinfo.md) | Public information about a registered [application](./kibana-plugin-core-public.app.md) | +| [PublicAppMetaInfo](./kibana-plugin-core-public.publicappmetainfo.md) | Public information about a registered app's [keywords](./kibana-plugin-core-public.appmeta.md) | | [PublicAppSearchDeepLinkInfo](./kibana-plugin-core-public.publicappsearchdeeplinkinfo.md) | Public information about a registered app's [searchDeepLinks](./kibana-plugin-core-public.appsearchdeeplink.md) | | [PublicUiSettingsParams](./kibana-plugin-core-public.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) exposed to the client-side. | | [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | diff --git a/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md index d56b0ac58cd9b..9f45a06935fe4 100644 --- a/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md +++ b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md @@ -9,10 +9,10 @@ Public information about a registered [application](./kibana-plugin-core-public. Signature: ```typescript -export declare type PublicAppInfo = Omit & { +export declare type PublicAppInfo = Omit & { status: AppStatus; navLinkStatus: AppNavLinkStatus; appRoute: string; - searchDeepLinks: PublicAppSearchDeepLinkInfo[]; + meta: PublicAppMetaInfo; }; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.publicappmetainfo.md b/docs/development/core/public/kibana-plugin-core-public.publicappmetainfo.md new file mode 100644 index 0000000000000..3ef0460aec467 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.publicappmetainfo.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PublicAppMetaInfo](./kibana-plugin-core-public.publicappmetainfo.md) + +## PublicAppMetaInfo type + +Public information about a registered app's [keywords](./kibana-plugin-core-public.appmeta.md) + +Signature: + +```typescript +export declare type PublicAppMetaInfo = Omit & { + keywords: string[]; + searchDeepLinks: PublicAppSearchDeepLinkInfo[]; +}; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.publicappsearchdeeplinkinfo.md b/docs/development/core/public/kibana-plugin-core-public.publicappsearchdeeplinkinfo.md index 9814f0408d047..e88cdb7d55edd 100644 --- a/docs/development/core/public/kibana-plugin-core-public.publicappsearchdeeplinkinfo.md +++ b/docs/development/core/public/kibana-plugin-core-public.publicappsearchdeeplinkinfo.md @@ -9,7 +9,8 @@ Public information about a registered app's [searchDeepLinks](./kibana-plugin-co Signature: ```typescript -export declare type PublicAppSearchDeepLinkInfo = Omit & { +export declare type PublicAppSearchDeepLinkInfo = Omit & { searchDeepLinks: PublicAppSearchDeepLinkInfo[]; + keywords: string[]; }; ``` 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 40fc1a8e05a68..7c53356615ee9 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 0000000000000..aaed18b3b8890 --- /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 027ae4209b77f..dbeeeb9979aae 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 62817cd0a1e33..2f844b6844645 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 0000000000000..5e397d11b0a89 --- /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 a370341000960..b318427012c0a 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 179148265e68d..b640ef1b89606 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 0000000000000..9438f38194493 --- /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 c5ea38278e820..1bbede5658942 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 0000000000000..50adef8268694 --- /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 06917fcac1b4d..9357ad7d5077e 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 8de3821161ab4..2040043d4351b 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 0000000000000..fe7bec70196c8 --- /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 274a475872b0b..7d70af4b535fe 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 b2cb217fecaa2..54f020e57cf4a 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 0000000000000..1255a6fe9f0ca --- /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 6559b4d7110be..b9b9f955c7ab5 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 b90018c3d9cdd..bd90f23b4ab59 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 8a3dbe5a6350c..88f85eb7a7d05 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 06e51958a2d1e..92926d10a543c 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 a2c2d9245eabe..753a3ff2ec6ec 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 f36f7b4ee77a4..0f14215ff1309 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 d3a62657372ac..0000000000000 --- 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 fdd31ca75be2a..957e3f279ff60 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 0595609b11e49..0000000000000 --- 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 5a1b5d1e06861..342163ed2e413 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 276499b435e1f..77e9c2d00b2dd 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 0000000000000..f00d015f316d2 --- /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 3676b744b8cc9..76b6708b93bd1 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 62610624655a1..2f5966f9ba940 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 a6aeba23cd280..b875b1fce4288 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 0000000000000..175e3fe947a0f --- /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 0000000000000..a85be3142d0f2 --- /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 931e474a41006..c22c8bc6b6245 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 8d74c8e555fee..461bf861d4d5e 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 273703cacca06..547608f40e6aa 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 b09433c6454ad..ca1c8eec8c2f7 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 94e66bf404f5c..0000000000000 --- 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 e1fb6d342457e..0000000000000 --- 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 fd1ea7df4fb74..76e347bddd168 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 e8baf44ff9cbc..a75637e8ea9d3 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 74b55d85f10e3..b69734cfc3233 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 fd784ff17fa84..0000000000000 --- 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 0d9fa2d83ee57..0000000000000 --- 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 426f17f9a0352..5603c852ad39d 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 b69bba892f475..ed76cfea97684 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 0ccf8aa3d7415..0000000000000 --- 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 2f0d22cf6dd74..da7a7a8bfe645 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 c5ef6843390b3..0000000000000 --- 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 129144a66cee5..0000000000000 --- 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 ca999322b7a56..f29d487d774e0 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 e95e7e1eb38b6..1ebb30c49c0b3 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 8e7fb8b8bbf29..b20f08520c43d 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 d540de7637441..300c46a47c47f 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 b996620686a28..95b737a8d6cae 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 f94b34ecc2d90..27c1b1eb48f16 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 dff958608ef9e..edb7d2d3a1551 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 e35eb503ab62b..4fe8431770dea 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 6f03777e14552..dee5f75f7c074 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 bd8d4dc50b8fd..0000000000000 --- 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 5c4fc284d83b1..0000000000000 --- 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/fleet/fleet.asciidoc b/docs/fleet/fleet.asciidoc index aac733ad8468c..4777800ce5d57 100644 --- a/docs/fleet/fleet.asciidoc +++ b/docs/fleet/fleet.asciidoc @@ -24,4 +24,4 @@ image::fleet/images/fleet-start.png[{fleet} app in {kib}] == Get started To get started with {fleet}, refer to the -{ingest-guide}/index.html[{fleet}] docs. +{fleet-guide}/index.html[{fleet}] docs. diff --git a/docs/getting-started/quick-start-guide.asciidoc b/docs/getting-started/quick-start-guide.asciidoc index ccb6e931d69e3..02abde9d03a3b 100644 --- a/docs/getting-started/quick-start-guide.asciidoc +++ b/docs/getting-started/quick-start-guide.asciidoc @@ -140,4 +140,4 @@ For more information, refer to <>. If you are you ready to add your own data, refer to <>. -If you want to ingest your data, refer to {ingest-guide}/fleet-quick-start.html[Quick start: Get logs and metrics into the Elastic Stack]. +If you want to ingest your data, refer to {fleet-guide}/fleet-quick-start.html[Quick start: Get logs and metrics into the Elastic Stack]. diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 6244a43b54f72..99fadb240335a 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -453,6 +453,9 @@ of buckets to try to represent. ==== Visualization [horizontal] +[[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.** Maps values to specific colors in *Visualize* charts and *TSVB*. This setting does not apply to *Lens*. diff --git a/docs/maps/connect-to-ems.asciidoc b/docs/maps/connect-to-ems.asciidoc index 45ced2e64aa73..a5b8010f21f97 100644 --- a/docs/maps/connect-to-ems.asciidoc +++ b/docs/maps/connect-to-ems.asciidoc @@ -35,3 +35,188 @@ To disable EMS, change your <> file. . Set `map.includeElasticMapsService` to `false` to turn off the EMS connection. . Set `map.tilemap.url` to the URL of your tile server. This configures the default tile layer of Maps. . (Optional) Set `map.regionmap` to the vector shapes of the administrative boundaries that you want to use. + +[float] +[id=elastic-maps-server] +=== Host Elastic Maps Service locally + +beta::[] + +If you cannot connect to Elastic Maps Service from the {kib} server or browser clients, and your cluster has the appropriate license level, you can opt to host the service on your own infrastructure. + +{hosted-ems} is a self-managed version of Elastic Maps Service offered as a Docker image that provides both the EMS basemaps and EMS boundaries. You must first download and run the image. After connecting it to your {es} cluster for license validation, you're guided to download and configure the basemaps database, which must be retrieved separately. + +IMPORTANT: {hosted-ems} does not serve raster tiles, needed by Vega, coordinate, and region map visualizations. + +You can use +docker pull+ to download the {hosted-ems} image from the Elastic Docker registry. + +ifeval::["{release-state}"=="unreleased"] +Version {version} of {hosted-ems} has not yet been released, so no Docker image is currently available for this version. +endif::[] + +ifeval::["{release-state}"!="unreleased"] + +["source","bash",subs="attributes"] +---------------------------------- +docker pull {ems-docker-image} +---------------------------------- + +Start {hosted-ems} and expose the default port `8080`: + +["source","bash",subs="attributes"] +---------------------------------- +docker run --rm --init --publish 8080:8080 \ + {ems-docker-image} +---------------------------------- + +Once {hosted-ems} is running, follow instructions from the webpage at `localhost:8080` to define a configuration file and download the basemaps database. + +[role="screenshot"] +image::images/elastic-maps-server-instructions.png[Set-up instructions] + +endif::[] + +[float] +[[elastic-maps-server-configuration]] +==== Configuration + +{hosted-ems} reads properties from a configuration file in YAML format that is validated on startup. The location of this file is provided by the `EMS_PATH_CONF` environment variable and defaults to `/usr/src/app/server/config/elastic-maps-server.yml`. + +*General settings* + +[cols="2*<"] +|=== +| [[ems-hostname]]`hostname` + | Specifies the host of the backend server. To allow remote users to connect, set the value to the IP address or DNS name of the {hosted-ems} container. *Default: _your-hostname_*. <>. + +| `port` + | Specifies the port used by the backend server. Default: *`8080`*. <>. + +| `ui` + | Controls the display of the status page and the layer preview. *Default: `true`* + +| `logging.level` + | Verbosity of {hosted-ems} logs. Valid values are `trace`, `debug`, `info`, `warn`, `error`, `fatal`, and `silent`. *Default: `info`* + +| `path.planet` + | Path of the basemaps database. *Default: `/usr/src/app/data/planet.mbtiles`* +|=== + + +*{es} connection and security settings* + +[cols="2*<"] +|=== + +| `elasticsearch.host` + | URL of the {es} instance to use for license validation. + +| `elasticsearch.username` and `elasticsearch.password` + | Credentials of a user with at least the `monitor` role. + +| `elasticsearch.ssl.certificateAuthorities` + | Paths to one or more PEM-encoded X.509 certificate authority (CA) certificates that make up a trusted certificate chain for {hosted-ems}. This chain is used by {hosted-ems} to establish trust when connecting to your {es} cluster. <>. + +| `elasticsearch.ssl.certificate` and `elasticsearch.ssl.key`, and `elasticsearch.ssl.keyPassphrase` + | Optional settings that provide the paths to the PEM-format SSL certificate and key files and the key password. These files are used to verify the identity of {hosted-ems} to {es} and are required when `xpack.security.http.ssl.client_authentication` in {es} is set to `required`. <>. + +| `elasticsearch.ssl.verificationMode` + | Controls the verification of the server certificate that {hosted-ems} receives when making an outbound SSL/TLS connection to {es}. Valid values are "`full`", "`certificate`", and "`none`". Using "`full`" performs hostname verification, using "`certificate`" skips hostname verification, and using "`none`" skips verification entirely. *Default: `full`*. <>. + +|=== + +*Server security settings* + +[cols="2*<"] +|=== + +| `ssl.enabled` + | Enables SSL/TLS for inbound connections to {hosted-ems}. When set to `true`, a certificate and its corresponding private key must be provided. *Default: `false`*. <>. + +| `ssl.certificateAuthorities` + | Paths to one or more PEM-encoded X.509 certificate authority (CA) certificates that make up a trusted certificate chain for {hosted-ems}. This chain is used by the {hosted-ems} to establish trust when receiving inbound SSL/TLS connections from end users. <>. + +| `ssl.key`, `ssl.certificate`, and `ssl.keyPassphrase` + | Location of yor SSL key and certificate files and the password that decrypts the private key that is specified via `ssl.key`. This password is optional, as the key may not be encrypted. <>. + +| `ssl.supportedProtocols` + | An array of supported protocols with versions. +Valid protocols: `TLSv1`, `TLSv1.1`, `TLSv1.2`, `TLSv1.3`. *Default: `TLSv1.1`, `TLSv1.2`, `TLSv1.3`*. <>. + +| `ssl.cipherSuites` + | Details on the format, and the valid options, are available via the +https://www.openssl.org/docs/man1.1.1/man1/ciphers.html#CIPHER-LIST-FORMAT[OpenSSL cipher list format documentation]. +*Default: `TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256 ECDHE-RSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-GCM-SHA384, DHE-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, DHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-SHA384, DHE-RSA-AES256-SHA384, ECDHE-RSA-AES256-SHA256, DHE-RSA-AES256-SHA256, HIGH,!aNULL, !eNULL, !EXPORT, !DES, !RC4, !MD5, !PSK, !SRP, !CAMELLIA`*. <>. +|=== + +[float] +[[elastic-maps-server-bind-mount-config]] +===== Bind-mounted configuration + +One way to configure {hosted-ems} is to provide `elastic-maps-server.yml` via bind-mounting. With +docker-compose+, the bind-mount can be specified like this: + +["source","yaml",subs="attributes"] +-------------------------------------------- +version: '2' +services: + {hosted-ems}: + image: {ems-docker-image} + volumes: + - ./elastic-maps-server.yml:/usr/src/app/config/elastic-maps-server.yml +-------------------------------------------- + +[float] +[[elastic-maps-server-envvar-config]] +===== Environment variable configuration +All configuration settings can be overridden by environment variables that are named with all uppercase letters and by replacing YAML periods with underscores. For example `elasticsearch.ssl.certificate` could be overridden by the environment variable `ELASTICSEARCH_SSL_CERTIFICATE`. Boolean variables must use the `true` or `false` strings. + +WARNING: All information that you include in environment variables is visible through the `ps` command, including sensitive information. + +These variables can be set with +docker-compose+ like this: + +["source","yaml",subs="attributes"] +---------------------------------------------------------- +version: '2' +services: + {hosted-ems}: + image: {ems-docker-image} + environment: + ELASTICSEARCH_HOST: http://elasticsearch.example.org + ELASTICSEARCH_USERNAME: 'ems' + ELASTICSEARCH_PASSWORD: 'changeme' +---------------------------------------------------------- + +[float] +[[elastic-maps-server-data]] +==== Data + +{hosted-ems} hosts vector layer boundaries and vector tile basemaps for the entire planet. Boundaries include world countries, global administrative regions, and specific country regions. A minimal basemap is provided with {hosted-ems}. This can be used for testing environments but won't be functional for normal operations. The full basemap (around 90GB file) needs to be mounted on the Docker container for {hosted-ems} to run normally. + +TIP: The available basemaps and boundaries can be explored from the `/maps` endpoint in a web page that is your self-managed equivalent to https://maps.elastic.co + + +[float] +[[elastic-maps-server-kibana]] +==== Kibana configuration + +With {hosted-ems} running, add the `map.emsUrl` configuration key in your <> file pointing to the root of the service. This setting will point {kib} to request EMS basemaps and boundaries from {hosted-ems}. Typically this will be the URL to the <> of {hosted-ems}. For example, `map.emsUrl: https://my-ems-server:8080`. + + +[float] +[[elastic-maps-server-check]] +==== Status check + +{hosted-ems} periodically runs a status check that is exposed in three different forms: + +* At the root of {hosted-ems}, a web page will render the status of the different services. +* A JSON representation of {hosted-ems} status is available at the `/status` endpoint. +* The Docker https://docs.docker.com/engine/reference/builder/#healthcheck[`HEALTHCHECK`] instruction is run by default and will inform about the health of the service, running a process equivalent to the `/status` endpoint. + +IMPORTANT: {hosted-ems} won't respond to any data request if the license validation is not fulfilled. + + +[float] +[[elastic-maps-server-logging]] +==== Logging + +Logs are generated in {ecs-ref}[ECS JSON format] and emitted to the standard output and to `/var/log/elastic-maps-server/elastic-maps-server.log`. The server won't rotate the logs automatically but the `logrotate` tool is installed in the image. Mount `/dev/null` to the default log path if you want to disable the output to that file. diff --git a/docs/maps/images/elastic-maps-server-instructions.png b/docs/maps/images/elastic-maps-server-instructions.png new file mode 100644 index 0000000000000..17e9163a845c2 Binary files /dev/null and b/docs/maps/images/elastic-maps-server-instructions.png differ diff --git a/docs/maps/index.asciidoc b/docs/maps/index.asciidoc index 3c3537826a6a8..59b592ba1ec59 100644 --- a/docs/maps/index.asciidoc +++ b/docs/maps/index.asciidoc @@ -1,3 +1,7 @@ +:ems-docker-repo: docker.elastic.co/elastic-maps-service/elastic-maps-server-ubi8 +:ems-docker-image: {ems-docker-repo}:{version} +:hosted-ems: Elastic Maps Server + [role="xpack"] [[maps]] = Maps diff --git a/docs/settings/fleet-settings.asciidoc b/docs/settings/fleet-settings.asciidoc index abfd2d3a95bed..2d330445d9ced 100644 --- a/docs/settings/fleet-settings.asciidoc +++ b/docs/settings/fleet-settings.asciidoc @@ -10,7 +10,7 @@ experimental[] You can configure `xpack.fleet` settings in your `kibana.yml`. By default, {fleet} is enabled. To use {fleet}, you also need to configure {kib} and {es} hosts. -See the {ingest-guide}/index.html[{fleet}] docs for more information. +See the {fleet-guide}/index.html[{fleet}] docs for more information. [[general-fleet-settings-kb]] ==== General {fleet} settings diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index 0af953ec2cb09..8c0aa12ffc4c6 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -37,7 +37,7 @@ ship with dashboards and visualizations, so you can quickly get insights into your data. To get started, refer to -{ingest-guide}/fleet-quick-start.html[Quick start: Get logs and metrics into the Elastic Stack]. +{fleet-guide}/fleet-quick-start.html[Quick start: Get logs and metrics into the Elastic Stack]. [role="screenshot"] image::images/add-data-fleet.png[Add data using Fleet] diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 8b50fc38167d3..febdf707dce9a 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -192,7 +192,7 @@ In addition to this setting, trusted certificates may be specified via <>. If the trust store has no password, leave this as blank. If the trust store has an empty password, set this to `""`. -| `elasticsearch.ssl.verificationMode:` +|[[elasticsearch-ssl-verificationMode]] `elasticsearch.ssl.verificationMode:` | Controls the verification of the server certificate that {kib} receives when making an outbound SSL/TLS connection to {es}. Valid values are `"full"`, `"certificate"`, and `"none"`. Using `"full"` performs hostname verification, @@ -526,7 +526,7 @@ users. If PKI authentication is enabled, this chain is also used by {kib} to ver In addition to this setting, trusted certificates may be specified via <> and/or <>. -| `server.ssl.cipherSuites:` +| [[server-ssl-cipherSuites]] `server.ssl.cipherSuites:` | Details on the format, and the valid options, are available via the https://www.openssl.org/docs/man1.1.1/man1/ciphers.html#CIPHER-LIST-FORMAT[OpenSSL cipher list format documentation]. *Default: `TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256 ECDHE-RSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-GCM-SHA384, DHE-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, DHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-SHA384, DHE-RSA-AES256-SHA384, ECDHE-RSA-AES256-SHA256, DHE-RSA-AES256-SHA256, HIGH,!aNULL, !eNULL, !EXPORT, !DES, !RC4, !MD5, !PSK, !SRP, !CAMELLIA`*. @@ -585,7 +585,7 @@ the trust store has no password, leave this unset. If the trust store has an emp | {kib} binds to this port and redirects all http requests to https over the port configured as <>. -| `server.ssl.supportedProtocols:` +| [[server-ssl-supportedProtocols]] `server.ssl.supportedProtocols:` | An array of supported protocols with versions. Valid protocols: `TLSv1`, `TLSv1.1`, `TLSv1.2`, `TLSv1.3`. *Default: TLSv1.1, TLSv1.2, TLSv1.3* diff --git a/docs/user/reporting/images/canvas-full-page-layout.png b/docs/user/reporting/images/canvas-full-page-layout.png new file mode 100644 index 0000000000000..06c94f05531b2 Binary files /dev/null and b/docs/user/reporting/images/canvas-full-page-layout.png differ diff --git a/docs/user/reporting/index.asciidoc b/docs/user/reporting/index.asciidoc index 224973d3c840c..6ebeafef1013e 100644 --- a/docs/user/reporting/index.asciidoc +++ b/docs/user/reporting/index.asciidoc @@ -92,6 +92,16 @@ To create a printer-friendly PDF with multiple A4 portrait pages and two visuali image::user/reporting/images/preserve-layout-switch.png["Share"] +[float] +[[full-page-pdf]] +== Full page PDF layout —Canvas only + +To create a PDF without margins surrounding the Canvas workpad, turn on *Full page layout* before generating the PDF. + +[role="screenshot"] +image::user/reporting/images/canvas-full-page-layout.png["Full Page Layout"] + + [float] [[manage-report-history]] == View and manage report history diff --git a/docs/user/security/audit-logging.asciidoc b/docs/user/security/audit-logging.asciidoc index 4b3512ae3056b..acb0f94cf878c 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 @@ -58,7 +60,6 @@ authorization checks have passed, but before the response from {es} is received. Refer to the corresponding {es} logs for potential write errors. ============================================================================ - [cols="3*<"] |====== 3+a| @@ -89,9 +90,12 @@ Refer to the corresponding {es} logs for potential write errors. | `failure` | User is not authorized to create a connector. .2+| `alert_create` -| `unknown` | User is creating an alert rule. -| `failure` | User is not authorized to create an alert rule. +| `unknown` | User is creating an alert. +| `failure` | User is not authorized to create an alert. +.2+| `space_create` +| `unknown` | User is creating a space. +| `failure` | User is not authorized to create a space. 3+a| ====== Type: change @@ -121,28 +125,28 @@ Refer to the corresponding {es} logs for potential write errors. | `failure` | User is not authorized to update a connector. .2+| `alert_update` -| `unknown` | User is updating an alert rule. -| `failure` | User is not authorized to update an alert rule. +| `unknown` | User is updating an alert. +| `failure` | User is not authorized to update an alert. .2+| `alert_update_api_key` -| `unknown` | User is updating the API key of an alert rule. -| `failure` | User is not authorized to update the API key of an alert rule. +| `unknown` | User is updating the API key of an alert. +| `failure` | User is not authorized to update the API key of an alert. .2+| `alert_enable` -| `unknown` | User is enabling an alert rule. -| `failure` | User is not authorized to enable an alert rule. +| `unknown` | User is enabling an alert. +| `failure` | User is not authorized to enable an alert. .2+| `alert_disable` -| `unknown` | User is disabling an alert rule. -| `failure` | User is not authorized to disable an alert rule. +| `unknown` | User is disabling an alert. +| `failure` | User is not authorized to disable an alert. .2+| `alert_mute` -| `unknown` | User is muting an alert rule. -| `failure` | User is not authorized to mute an alert rule. +| `unknown` | User is muting an alert. +| `failure` | User is not authorized to mute an alert. .2+| `alert_unmute` -| `unknown` | User is unmuting an alert rule. -| `failure` | User is not authorized to unmute an alert rule. +| `unknown` | User is unmuting an alert. +| `failure` | User is not authorized to unmute an alert. .2+| `alert_instance_mute` | `unknown` | User is muting an alert instance. @@ -152,6 +156,9 @@ Refer to the corresponding {es} logs for potential write errors. | `unknown` | User is unmuting an alert instance. | `failure` | User is not authorized to unmute an alert instance. +.2+| `space_update` +| `unknown` | User is updating a space. +| `failure` | User is not authorized to update a space. 3+a| ====== Type: deletion @@ -169,8 +176,12 @@ Refer to the corresponding {es} logs for potential write errors. | `failure` | User is not authorized to delete a connector. .2+| `alert_delete` -| `unknown` | User is deleting an alert rule. -| `failure` | User is not authorized to delete an alert rule. +| `unknown` | User is deleting an alert. +| `failure` | User is not authorized to delete an alert. + +.2+| `space_delete` +| `unknown` | User is deleting a space. +| `failure` | User is not authorized to delete a space. 3+a| ====== Type: access @@ -196,13 +207,20 @@ Refer to the corresponding {es} logs for potential write errors. | `failure` | User is not authorized to search for connectors. .2+| `alert_get` -| `success` | User has accessed an alert rule. -| `failure` | User is not authorized to access an alert rule. +| `success` | User has accessed an alert. +| `failure` | User is not authorized to access an alert. .2+| `alert_find` -| `success` | User has accessed an alert rule as part of a search operation. -| `failure` | User is not authorized to search for alert rules. +| `success` | User has accessed an alert as part of a search operation. +| `failure` | User is not authorized to search for alerts. +.2+| `space_get` +| `success` | User has accessed a space. +| `failure` | User is not authorized to access a space. + +.2+| `space_find` +| `success` | User has accessed a space as part of a search operation. +| `failure` | User is not authorized to search for spaces. 3+a| ===== Category: web @@ -214,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 0000000000000..58c0c0bb775ca --- /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 f84e9de87c734..6a5c4a83aa3ad 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 b4b3cbe22b715..6e8809063ca57 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 68bcc37c65600..eaf353b3e55d0 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/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index c58d010a1f317..08d883a7cbb4d 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -6,7 +6,7 @@ pageLoadAssetSize: beatsManagement: 188135 bfetch: 41874 canvas: 1066647 - charts: 159211 + charts: 195358 cloud: 21076 console: 46091 core: 692106 @@ -98,7 +98,7 @@ pageLoadAssetSize: visTypeTimeseries: 155203 visTypeVega: 153573 visTypeVislib: 242838 - visTypeXy: 20255 + visTypeXy: 113478 visualizations: 295025 visualize: 57431 watcher: 43598 diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index 89ed60909aa55..55514da3e231f 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -111,7 +111,7 @@ module.exports = { // An array of regexp pattern strings that are matched against all source file paths, matched files to include/exclude for code coverage collectCoverageFrom: [ '**/*.{js,mjs,jsx,ts,tsx}', - '!**/{__test__,__snapshots__,__examples__,mocks,tests,test_helpers,integration_tests,types}/**/*', + '!**/{__test__,__snapshots__,__examples__,*mock*,tests,test_helpers,integration_tests,types}/**/*', '!**/*mock*.ts', '!**/*.test.ts', '!**/*.d.ts', diff --git a/src/core/public/application/index.ts b/src/core/public/application/index.ts index b39aa70c888fe..96d669a7b96f4 100644 --- a/src/core/public/application/index.ts +++ b/src/core/public/application/index.ts @@ -31,6 +31,7 @@ export { AppNavLinkStatus, AppUpdatableFields, AppUpdater, + AppMeta, AppSearchDeepLink, ApplicationSetup, ApplicationStart, @@ -41,6 +42,7 @@ export { AppLeaveConfirmAction, NavigateToAppOptions, PublicAppInfo, + PublicAppMetaInfo, PublicAppSearchDeepLinkInfo, // Internal types InternalApplicationSetup, diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index c161a7f166541..0a31490ad664c 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -83,7 +83,7 @@ export enum AppNavLinkStatus { */ export type AppUpdatableFields = Pick< App, - 'status' | 'navLinkStatus' | 'tooltip' | 'defaultPath' | 'searchDeepLinks' + 'status' | 'navLinkStatus' | 'tooltip' | 'defaultPath' | 'meta' >; /** @@ -237,39 +237,76 @@ export interface App { exactRoute?: boolean; /** - * Array of links that represent secondary in-app locations for the app. + * Meta data for an application that represent additional information for the app. + * See {@link AppMeta} * * @remarks * Used to populate navigational search results (where available). - * Can be updated using the {@link App.updater$} observable. See {@link AppSubLink} for more details. + * Can be updated using the {@link App.updater$} observable. See {@link PublicAppSearchDeepLinkInfo} for more details. * * @example - * The `path` property on deep links should not include the application's `appRoute`: * ```ts * core.application.register({ * id: 'my_app', - * title: 'My App', - * searchDeepLinks: [ - * { id: 'sub1', title: 'Sub1', path: '/sub1' }, + * title: 'Translated title', + * meta: { + * keywords: ['translated keyword1', 'translated keyword2'], + * searchDeepLinks: [ + * { id: 'sub1', title: 'Sub1', path: '/sub1', keywords: ['subpath1'] }, * { * id: 'sub2', * title: 'Sub2', * searchDeepLinks: [ - * { id: 'subsub', title: 'SubSub', path: '/sub2/sub' } + * { id: 'subsub', title: 'SubSub', path: '/sub2/sub', keywords: ['subpath2'] } * ] * } * ], - * mount: () => { ... }, + * }, + * mount: () => { ... } * }) * ``` - * - * Will produce deep links on these paths: - * - `/app/my_app/sub1` - * - `/app/my_app/sub2/sub` */ + meta?: AppMeta; +} + +/** + * Input type for meta data for an application. + * + * Meta fields include `keywords` and `searchDeepLinks` + * Keywords is an array of string with which to associate the app, must include at least one unique string as an array. + * `searchDeepLinks` is an array of links that represent secondary in-app locations for the app. + * @public + */ +export interface AppMeta { + /** Keywords to represent this application */ + keywords?: string[]; + /** Array of links that represent secondary in-app locations for the app. */ searchDeepLinks?: AppSearchDeepLink[]; } +/** + * Public information about a registered app's {@link AppMeta | keywords } + * + * @public + */ +export type PublicAppMetaInfo = Omit & { + keywords: string[]; + searchDeepLinks: PublicAppSearchDeepLinkInfo[]; +}; + +/** + * Public information about a registered app's {@link AppSearchDeepLink | searchDeepLinks} + * + * @public + */ +export type PublicAppSearchDeepLinkInfo = Omit< + AppSearchDeepLink, + 'searchDeepLinks' | 'keywords' +> & { + searchDeepLinks: PublicAppSearchDeepLinkInfo[]; + keywords: string[]; +}; + /** * Input type for registering secondary in-app locations for an application. * @@ -289,35 +326,30 @@ export type AppSearchDeepLink = { path: string; /** Optional array of links that are 'underneath' this section in the hierarchy */ searchDeepLinks?: AppSearchDeepLink[]; + /** Optional keywords to match with in deep links search for the page at the path */ + keywords?: string[]; } | { /** Optional path to access this section. Omit if this part of the hierarchy does not have a page URL. */ path?: string; /** Array links that are 'underneath' this section in this hierarchy. */ searchDeepLinks: AppSearchDeepLink[]; + /** Optional keywords to match with in deep links search. Omit if this part of the hierarchy does not have a page URL. */ + keywords?: string[]; } ); -/** - * Public information about a registered app's {@link AppSearchDeepLink | searchDeepLinks} - * - * @public - */ -export type PublicAppSearchDeepLinkInfo = Omit & { - searchDeepLinks: PublicAppSearchDeepLinkInfo[]; -}; - /** * Public information about a registered {@link App | application} * * @public */ -export type PublicAppInfo = Omit & { +export type PublicAppInfo = Omit & { // remove optional on fields populated with default values status: AppStatus; navLinkStatus: AppNavLinkStatus; appRoute: string; - searchDeepLinks: PublicAppSearchDeepLinkInfo[]; + meta: PublicAppMetaInfo; }; /** diff --git a/src/core/public/application/utils/get_app_info.test.ts b/src/core/public/application/utils/get_app_info.test.ts index ee0bd4f1eadfa..ff09a18c8b655 100644 --- a/src/core/public/application/utils/get_app_info.test.ts +++ b/src/core/public/application/utils/get_app_info.test.ts @@ -43,19 +43,24 @@ describe('getAppInfo', () => { status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.visible, appRoute: `/app/some-id`, - searchDeepLinks: [], + meta: { + keywords: [], + searchDeepLinks: [], + }, }); }); it('populates default values for nested searchDeepLinks', () => { const app = createApp({ - searchDeepLinks: [ - { - id: 'sub-id', - title: 'sub-title', - searchDeepLinks: [{ id: 'sub-sub-id', title: 'sub-sub-title', path: '/sub-sub' }], - }, - ], + meta: { + searchDeepLinks: [ + { + id: 'sub-id', + title: 'sub-title', + searchDeepLinks: [{ id: 'sub-sub-id', title: 'sub-sub-title', path: '/sub-sub' }], + }, + ], + }, }); const info = getAppInfo(app); @@ -65,20 +70,25 @@ describe('getAppInfo', () => { status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.visible, appRoute: `/app/some-id`, - searchDeepLinks: [ - { - id: 'sub-id', - title: 'sub-title', - searchDeepLinks: [ - { - id: 'sub-sub-id', - title: 'sub-sub-title', - path: '/sub-sub', - searchDeepLinks: [], // default empty array added - }, - ], - }, - ], + meta: { + keywords: [], + searchDeepLinks: [ + { + id: 'sub-id', + title: 'sub-title', + keywords: [], + searchDeepLinks: [ + { + id: 'sub-sub-id', + title: 'sub-sub-title', + path: '/sub-sub', + keywords: [], + searchDeepLinks: [], // default empty array added + }, + ], + }, + ], + }, }); }); @@ -108,4 +118,53 @@ describe('getAppInfo', () => { }) ); }); + + it('adds default meta fields to sublinks when needed', () => { + const app = createApp({ + meta: { + searchDeepLinks: [ + { + id: 'sub-id', + title: 'sub-title', + searchDeepLinks: [ + { + id: 'sub-sub-id', + title: 'sub-sub-title', + path: '/sub-sub', + keywords: ['sub sub'], + }, + ], + }, + ], + }, + }); + const info = getAppInfo(app); + + expect(info).toEqual({ + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.visible, + appRoute: `/app/some-id`, + meta: { + keywords: [], + searchDeepLinks: [ + { + id: 'sub-id', + title: 'sub-title', + keywords: [], // default empty array + searchDeepLinks: [ + { + id: 'sub-sub-id', + title: 'sub-sub-title', + path: '/sub-sub', + keywords: ['sub sub'], + searchDeepLinks: [], + }, + ], + }, + ], + }, + }); + }); }); diff --git a/src/core/public/application/utils/get_app_info.ts b/src/core/public/application/utils/get_app_info.ts index 7316080816da7..574696c3bd5f2 100644 --- a/src/core/public/application/utils/get_app_info.ts +++ b/src/core/public/application/utils/get_app_info.ts @@ -39,7 +39,10 @@ export function getAppInfo(app: App): PublicAppInfo { status: app.status!, navLinkStatus, appRoute: app.appRoute!, - searchDeepLinks: getSearchDeepLinkInfos(app, app.searchDeepLinks), + meta: { + keywords: app.meta?.keywords ?? [], + searchDeepLinks: getSearchDeepLinkInfos(app, app.meta?.searchDeepLinks), + }, }; } @@ -57,6 +60,7 @@ function getSearchDeepLinkInfos( id: rawDeepLink.id, title: rawDeepLink.title, path: rawDeepLink.path, + keywords: rawDeepLink.keywords ?? [], searchDeepLinks: getSearchDeepLinkInfos(app, rawDeepLink.searchDeepLinks), }; } diff --git a/src/core/public/chrome/nav_links/to_nav_link.test.ts b/src/core/public/chrome/nav_links/to_nav_link.test.ts index 606370c5afd0a..44feb133cfcec 100644 --- a/src/core/public/chrome/nav_links/to_nav_link.test.ts +++ b/src/core/public/chrome/nav_links/to_nav_link.test.ts @@ -28,7 +28,10 @@ const app = (props: Partial = {}): PublicAppInfo => ({ status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.default, appRoute: `/app/some-id`, - searchDeepLinks: [], + meta: { + keywords: [], + searchDeepLinks: [], + }, ...props, }); diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index b8843b5c85595..2893f4c9a9878 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -147,6 +147,7 @@ export class DocLinksService { featureImportance: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-feature-importance.html`, outlierDetectionRoc: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-dfanalytics-evaluate.html#ml-dfanalytics-roc`, regressionEvaluation: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-dfanalytics-evaluate.html#ml-dfanalytics-regression-evaluation`, + classificationAucRoc: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-dfanalytics-evaluate.html#ml-dfanalytics-class-aucroc`, }, transforms: { guide: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/transforms.html`, diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 2e1238df350e0..51375072d3e5a 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -108,10 +108,12 @@ export { AppLeaveConfirmAction, AppStatus, AppNavLinkStatus, + AppMeta, AppUpdatableFields, AppUpdater, AppSearchDeepLink, PublicAppInfo, + PublicAppMetaInfo, PublicAppSearchDeepLinkInfo, ScopedHistory, NavigateToAppOptions, diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index defe43377172c..0303eb62b6419 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -56,11 +56,10 @@ export interface App { exactRoute?: boolean; icon?: string; id: string; + meta?: AppMeta; mount: AppMount | AppMountDeprecated; navLinkStatus?: AppNavLinkStatus; order?: number; - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "AppSubLink" - searchDeepLinks?: AppSearchDeepLink[]; status?: AppStatus; title: string; tooltip?: string; @@ -137,6 +136,12 @@ export interface ApplicationStart { registerMountContext(contextName: T, provider: IContextProvider): void; } +// @public +export interface AppMeta { + keywords?: string[]; + searchDeepLinks?: AppSearchDeepLink[]; +} + // @public export type AppMount = (params: AppMountParameters) => AppUnmount | Promise; @@ -186,9 +191,11 @@ export type AppSearchDeepLink = { } & ({ path: string; searchDeepLinks?: AppSearchDeepLink[]; + keywords?: string[]; } | { path?: string; searchDeepLinks: AppSearchDeepLink[]; + keywords?: string[]; }); // @public @@ -201,7 +208,7 @@ export enum AppStatus { export type AppUnmount = () => void; // @public -export type AppUpdatableFields = Pick; +export type AppUpdatableFields = Pick; // @public export type AppUpdater = (app: App) => Partial | undefined; @@ -1008,16 +1015,23 @@ export interface PluginInitializerContext export type PluginOpaqueId = symbol; // @public -export type PublicAppInfo = Omit & { +export type PublicAppInfo = Omit & { status: AppStatus; navLinkStatus: AppNavLinkStatus; appRoute: string; + meta: PublicAppMetaInfo; +}; + +// @public +export type PublicAppMetaInfo = Omit & { + keywords: string[]; searchDeepLinks: PublicAppSearchDeepLinkInfo[]; }; // @public -export type PublicAppSearchDeepLinkInfo = Omit & { +export type PublicAppSearchDeepLinkInfo = Omit & { searchDeepLinks: PublicAppSearchDeepLinkInfo[]; + keywords: string[]; }; // @public diff --git a/src/core/server/elasticsearch/legacy/errors.ts b/src/core/server/elasticsearch/legacy/errors.ts index e557e7395fe56..adc1fa0728784 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 71040598d34b1..cbb60480c4cf1 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 43f5264ff22e3..42e89b66d9c51 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 42179374ec672..9efcf46148e1f 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 5a4b7e9f77582..7d141e81ddf36 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 63acd2207ac3a..d80c21bde8de8 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 b1e092ba5786a..ebc41a793f3b3 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/migrationsv2/actions/catch_retryable_es_client_errors.test.ts b/src/core/server/saved_objects/migrationsv2/actions/catch_retryable_es_client_errors.test.ts index 3186d7456383a..33787e3fce53a 100644 --- a/src/core/server/saved_objects/migrationsv2/actions/catch_retryable_es_client_errors.test.ts +++ b/src/core/server/saved_objects/migrationsv2/actions/catch_retryable_es_client_errors.test.ts @@ -22,14 +22,14 @@ import { elasticsearchClientMock } from '../../../elasticsearch/client/mocks'; import { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; describe('catchRetryableEsClientErrors', () => { - it('rejects non-retryable response errors', () => { + it('rejects non-retryable response errors', async () => { const error = new esErrors.ResponseError( elasticsearchClientMock.createApiResponse({ body: { error: { type: 'cluster_block_exception' } }, statusCode: 400, }) ); - return expect(Promise.reject(error).catch(catchRetryableEsClientErrors)).rejects.toBe(error); + await expect(Promise.reject(error).catch(catchRetryableEsClientErrors)).rejects.toBe(error); }); describe('returns left retryable_es_client_error for', () => { it('NoLivingConnectionsError', async () => { diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts index 937172272f179..05432d65c0558 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts @@ -100,8 +100,9 @@ describe('migration actions', () => { describe('fetchIndices', () => { it('resolves right empty record if no indices were found', async () => { + expect.assertions(1); const task = fetchIndices(client, ['no_such_index']); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", "right": Object {}, @@ -109,12 +110,13 @@ describe('migration actions', () => { `); }); it('resolves right record with found indices', async () => { + expect.assertions(1); const res = (await fetchIndices(client, [ 'no_such_index', 'existing_index_with_docs', ])()) as Either.Right; - return expect(res.right).toEqual( + expect(res.right).toEqual( expect.objectContaining({ existing_index_with_docs: { aliases: {}, @@ -131,17 +133,19 @@ describe('migration actions', () => { await createIndex(client, 'new_index_without_write_block', { properties: {} })(); }); it('resolves right when setting the write block succeeds', async () => { + expect.assertions(1); const task = setWriteBlock(client, 'new_index_without_write_block'); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", "right": "set_write_block_succeeded", } `); }); - it('resolves right when setting a write block on an index that already has one', () => { + it('resolves right when setting a write block on an index that already has one', async () => { + expect.assertions(1); const task = setWriteBlock(client, 'existing_index_with_write_block'); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", "right": "set_write_block_succeeded", @@ -149,6 +153,7 @@ describe('migration actions', () => { `); }); it('once resolved, prevents further writes to the index', async () => { + expect.assertions(1); const task = setWriteBlock(client, 'new_index_without_write_block'); await task(); const sourceDocs = ([ @@ -157,13 +162,14 @@ describe('migration actions', () => { { _source: { title: 'doc 3' } }, { _source: { title: 'doc 4' } }, ] as unknown) as SavedObjectsRawDoc[]; - return expect( + await expect( bulkOverwriteTransformedDocuments(client, 'new_index_without_write_block', sourceDocs)() ).rejects.toMatchObject(expect.anything()); }); - it('resolves left index_not_found_exception when the index does not exist', () => { + it('resolves left index_not_found_exception when the index does not exist', async () => { + expect.assertions(1); const task = setWriteBlock(client, 'no_such_index'); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", "left": Object { @@ -180,43 +186,51 @@ describe('migration actions', () => { await createIndex(client, 'existing_index_with_write_block_2', { properties: {} })(); await setWriteBlock(client, 'existing_index_with_write_block_2')(); }); - it('resolves right if successful when an index already has a write block', () => { + it('resolves right if successful when an index already has a write block', async () => { + expect.assertions(1); const task = removeWriteBlock(client, 'existing_index_with_write_block_2'); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", "right": "remove_write_block_succeeded", } `); }); - it('resolves right if successful when an index does not have a write block', () => { + it('resolves right if successful when an index does not have a write block', async () => { + expect.assertions(1); const task = removeWriteBlock(client, 'existing_index_without_write_block_2'); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", "right": "remove_write_block_succeeded", } `); }); - it('rejects if there is a non-retryable error', () => { + it('rejects if there is a non-retryable error', async () => { + expect.assertions(1); const task = removeWriteBlock(client, 'no_such_index'); - return expect(task()).rejects.toMatchInlineSnapshot( + await expect(task()).rejects.toMatchInlineSnapshot( `[ResponseError: index_not_found_exception]` ); }); }); describe('cloneIndex', () => { - afterEach(async () => { + afterAll(async () => { try { - await client.indices.delete({ index: 'yellow_then_green_index' }); + await client.indices.delete({ index: 'clone_*' }); } catch (e) { /** ignore */ } }); - it('resolves right if cloning into a new target index', () => { - const task = cloneIndex(client, 'existing_index_with_write_block', 'yellow_then_green_index'); - expect(task()).resolves.toMatchInlineSnapshot(` + it('resolves right if cloning into a new target index', async () => { + expect.assertions(1); + const task = cloneIndex( + client, + 'existing_index_with_write_block', + 'clone_yellow_then_green_index_1' + ); + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", "right": Object { @@ -226,10 +240,11 @@ describe('migration actions', () => { } `); }); - it.skip('resolves right after waiting for index status to be green if clone target already existed', async () => { + it('resolves right after waiting for index status to be green if clone target already existed', async () => { + expect.assertions(2); // Create a yellow index await client.indices.create({ - index: 'yellow_then_green_index', + index: 'clone_yellow_then_green_index_2', body: { mappings: { properties: {} }, settings: { @@ -243,7 +258,7 @@ describe('migration actions', () => { const cloneIndexPromise = cloneIndex( client, 'existing_index_with_write_block', - 'yellow_then_green_index' + 'clone_yellow_then_green_index_2' )(); let indexGreen = false; @@ -258,7 +273,7 @@ describe('migration actions', () => { indexGreen = true; }, 10); - return cloneIndexPromise.then((res) => { + await cloneIndexPromise.then((res) => { // Assert that the promise didn't resolve before the index became green expect(indexGreen).toBe(true); expect(res).toMatchInlineSnapshot(` @@ -272,9 +287,10 @@ describe('migration actions', () => { `); }); }); - it('resolves left index_not_found_exception if the source index does not exist', () => { - const task = cloneIndex(client, 'no_such_index', 'yellow_then_green_index'); - expect(task()).resolves.toMatchInlineSnapshot(` + it('resolves left index_not_found_exception if the source index does not exist', async () => { + expect.assertions(1); + const task = cloneIndex(client, 'no_such_index', 'clone_yellow_then_green_index_3'); + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", "left": Object { @@ -289,6 +305,7 @@ describe('migration actions', () => { // Reindex doesn't return any errors on it's own, so we have to test // together with waitForReindexTask describe('reindex & waitForReindexTask', () => { + expect.assertions(2); it('resolves right when reindex succeeds without reindex script', async () => { const res = (await reindex( client, @@ -320,6 +337,7 @@ describe('migration actions', () => { `); }); it('resolves right when reindex succeeds with reindex script', async () => { + expect.assertions(2); const res = (await reindex( client, 'existing_index_with_docs', @@ -349,6 +367,7 @@ describe('migration actions', () => { `); }); it('resolves right, ignores version conflicts and does not update existing docs when reindex multiple times', async () => { + expect.assertions(3); // Reindex with a script let res = (await reindex( client, @@ -397,6 +416,7 @@ describe('migration actions', () => { `); }); it('resolves right and proceeds to add missing documents if there are some existing docs conflicts', async () => { + expect.assertions(2); // Simulate a reindex that only adds some of the documents from the // source index into the target index await createIndex(client, 'reindex_target_4', { properties: {} })(); @@ -444,6 +464,7 @@ describe('migration actions', () => { `); }); it('resolves left incompatible_mapping_exception if all reindex failures are due to a strict_dynamic_mapping_exception', async () => { + expect.assertions(1); // Simulates one instance having completed the UPDATE_TARGET_MAPPINGS // step which makes the mappings incompatible with outdated documents. // If another instance then tries a reindex it will get a @@ -479,6 +500,7 @@ describe('migration actions', () => { `); }); it('resolves left incompatible_mapping_exception if all reindex failures are due to a mapper_parsing_exception', async () => { + expect.assertions(1); // Simulates one instance having completed the UPDATE_TARGET_MAPPINGS // step which makes the mappings incompatible with outdated documents. // If another instance then tries a reindex it will get a @@ -512,6 +534,7 @@ describe('migration actions', () => { `); }); it('resolves left index_not_found_exception if source index does not exist', async () => { + expect.assertions(1); const res = (await reindex( client, 'no_such_index', @@ -520,7 +543,7 @@ describe('migration actions', () => { false )()) as Either.Right; const task = waitForReindexTask(client, res.right.taskId, '10s'); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", "left": Object { @@ -531,6 +554,7 @@ describe('migration actions', () => { `); }); it('resolves left target_index_had_write_block if all failures are due to a write block', async () => { + expect.assertions(1); const res = (await reindex( client, 'existing_index_with_docs', @@ -541,7 +565,7 @@ describe('migration actions', () => { const task = waitForReindexTask(client, res.right.taskId, '10s'); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", "left": Object { @@ -551,6 +575,7 @@ describe('migration actions', () => { `); }); it('resolves left if requireAlias=true and the target is not an alias', async () => { + expect.assertions(1); const res = (await reindex( client, 'existing_index_with_docs', @@ -561,7 +586,7 @@ describe('migration actions', () => { const task = waitForReindexTask(client, res.right.taskId, '10s'); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", "left": Object { @@ -575,6 +600,7 @@ describe('migration actions', () => { describe('verifyReindex', () => { it('resolves right if source and target indices have the same amount of documents', async () => { + expect.assertions(1); const res = (await reindex( client, 'existing_index_with_docs', @@ -592,9 +618,10 @@ describe('migration actions', () => { } `); }); - it('resolves left if source and target indices have different amount of documents', () => { + it('resolves left if source and target indices have different amount of documents', async () => { + expect.assertions(1); const task = verifyReindex(client, 'existing_index_with_docs', 'existing_index_2'); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", "left": Object { @@ -604,6 +631,7 @@ describe('migration actions', () => { `); }); it('rejects if source or target index does not exist', async () => { + expect.assertions(2); let task = verifyReindex(client, 'no_such_index', 'existing_index_2'); await expect(task()).rejects.toMatchInlineSnapshot( `[ResponseError: index_not_found_exception]` @@ -618,6 +646,7 @@ describe('migration actions', () => { describe('searchForOutdatedDocuments', () => { it('only returns documents that match the outdatedDocumentsQuery', async () => { + expect.assertions(2); const resultsWithQuery = ((await searchForOutdatedDocuments( client, 'existing_index_with_docs', @@ -635,6 +664,7 @@ describe('migration actions', () => { expect(resultsWithoutQuery.length).toBe(4); }); it('resolves with _id, _source, _seq_no and _primary_term', async () => { + expect.assertions(1); const results = ((await searchForOutdatedDocuments(client, 'existing_index_with_docs', { match: { title: { query: 'doc' } }, })()) as Either.Right).right.outdatedDocuments; @@ -655,6 +685,7 @@ describe('migration actions', () => { describe('waitForPickupUpdatedMappingsTask', () => { it('rejects if there are failures', async () => { + expect.assertions(1); const res = (await pickupUpdatedMappings( client, 'existing_index_with_write_block' @@ -664,11 +695,12 @@ describe('migration actions', () => { // We can't do a snapshot match because the response includes an index // id which ES assigns dynamically - return expect(task()).rejects.toMatchObject({ + await expect(task()).rejects.toMatchObject({ message: /pickupUpdatedMappings task failed with the following failures:\n\[\{\"index\":\"existing_index_with_write_block\"/, }); }); it('rejects if there is an error', async () => { + expect.assertions(1); const res = (await pickupUpdatedMappings( client, 'no_such_index' @@ -676,12 +708,13 @@ describe('migration actions', () => { const task = waitForPickupUpdatedMappingsTask(client, res.right.taskId, '10s'); - return expect(task()).rejects.toMatchInlineSnapshot(` + await expect(task()).rejects.toMatchInlineSnapshot(` [Error: pickupUpdatedMappings task failed with the following error: {"type":"index_not_found_exception","reason":"no such index [no_such_index]","resource.type":"index_or_alias","resource.id":"no_such_index","index_uuid":"_na_","index":"no_such_index"}] `); }); it('resolves right when successful', async () => { + expect.assertions(1); const res = (await pickupUpdatedMappings( client, 'existing_index_with_docs' @@ -689,7 +722,7 @@ describe('migration actions', () => { const task = waitForPickupUpdatedMappingsTask(client, res.right.taskId, '10s'); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", "right": "pickup_updated_mappings_succeeded", @@ -700,6 +733,7 @@ describe('migration actions', () => { describe('updateAndPickupMappings', () => { it('resolves right when mappings were updated and picked up', async () => { + expect.assertions(3); // Create an index without any mappings and insert documents into it await createIndex(client, 'existing_index_without_mappings', { dynamic: false as any, @@ -741,13 +775,14 @@ describe('migration actions', () => { 'existing_index_without_mappings', { match: { title: { query: 'doc' } } } )()) as Either.Right).right.outdatedDocuments; - return expect(pickedUpSearchResults.length).toBe(4); + expect(pickedUpSearchResults.length).toBe(4); }); }); describe('updateAliases', () => { describe('remove', () => { - it('resolves left index_not_found_exception when the index does not exist', () => { + it('resolves left index_not_found_exception when the index does not exist', async () => { + expect.assertions(1); const task = updateAliases(client, [ { remove: { @@ -757,7 +792,7 @@ describe('migration actions', () => { }, }, ]); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", "left": Object { @@ -769,6 +804,7 @@ describe('migration actions', () => { }); describe('with must_exist=false', () => { it('resolves left alias_not_found_exception when alias does not exist', async () => { + expect.assertions(1); const task = updateAliases(client, [ { remove: { @@ -778,7 +814,7 @@ describe('migration actions', () => { }, }, ]); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", "left": Object { @@ -790,6 +826,7 @@ describe('migration actions', () => { }); describe('with must_exist=true', () => { it('resolves left alias_not_found_exception when alias does not exist on specified index', async () => { + expect.assertions(1); const task = updateAliases(client, [ { remove: { @@ -799,7 +836,7 @@ describe('migration actions', () => { }, }, ]); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", "left": Object { @@ -809,6 +846,7 @@ describe('migration actions', () => { `); }); it('resolves left alias_not_found_exception when alias does not exist', async () => { + expect.assertions(1); const task = updateAliases(client, [ { remove: { @@ -818,7 +856,7 @@ describe('migration actions', () => { }, }, ]); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", "left": Object { @@ -830,7 +868,8 @@ describe('migration actions', () => { }); }); describe('remove_index', () => { - it('left index_not_found_exception if index does not exist', () => { + it('left index_not_found_exception if index does not exist', async () => { + expect.assertions(1); const task = updateAliases(client, [ { remove_index: { @@ -838,7 +877,7 @@ describe('migration actions', () => { }, }, ]); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", "left": Object { @@ -848,7 +887,8 @@ describe('migration actions', () => { } `); }); - it('left remove_index_not_a_concrete_index when remove_index targets an alias', () => { + it('left remove_index_not_a_concrete_index when remove_index targets an alias', async () => { + expect.assertions(1); const task = updateAliases(client, [ { remove_index: { @@ -856,7 +896,7 @@ describe('migration actions', () => { }, }, ]); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", "left": Object { @@ -872,7 +912,8 @@ describe('migration actions', () => { afterAll(async () => { await client.indices.delete({ index: 'yellow_then_green_index' }); }); - it.skip('resolves right after waiting for an index status to be green if the index already existed', async () => { + it('resolves right after waiting for an index status to be green if the index already existed', async () => { + expect.assertions(2); // Create a yellow index await client.indices.create( { @@ -903,7 +944,7 @@ describe('migration actions', () => { indexGreen = true; }, 10); - return createIndexPromise.then((res) => { + await createIndexPromise.then((res) => { // Assert that the promise didn't resolve before the index became green expect(indexGreen).toBe(true); expect(res).toMatchInlineSnapshot(` @@ -914,24 +955,26 @@ describe('migration actions', () => { `); }); }); - it('rejects when there is an unexpected error creating the index', () => { + it('rejects when there is an unexpected error creating the index', async () => { + expect.assertions(1); // Creating an index with the same name as an existing alias to induce // failure - expect( + await expect( createIndex(client, 'existing_index_2_alias', undefined as any)() ).rejects.toMatchInlineSnapshot(`[ResponseError: invalid_index_name_exception]`); }); }); describe('bulkOverwriteTransformedDocuments', () => { - it('resolves right when documents do not yet exist in the index', () => { + it('resolves right when documents do not yet exist in the index', async () => { + expect.assertions(1); const newDocs = ([ { _source: { title: 'doc 5' } }, { _source: { title: 'doc 6' } }, { _source: { title: 'doc 7' } }, ] as unknown) as SavedObjectsRawDoc[]; const task = bulkOverwriteTransformedDocuments(client, 'existing_index_with_docs', newDocs); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", "right": "bulk_index_succeeded", @@ -939,6 +982,7 @@ describe('migration actions', () => { `); }); it('resolves right even if there were some version_conflict_engine_exception', async () => { + expect.assertions(1); const existingDocs = ((await searchForOutdatedDocuments( client, 'existing_index_with_docs', @@ -949,20 +993,21 @@ describe('migration actions', () => { ...existingDocs, ({ _source: { title: 'doc 8' } } as unknown) as SavedObjectsRawDoc, ]); - return expect(task()).resolves.toMatchInlineSnapshot(` + await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", "right": "bulk_index_succeeded", } `); }); - it('rejects if there are errors', () => { + it('rejects if there are errors', async () => { + expect.assertions(1); const newDocs = ([ { _source: { title: 'doc 5' } }, { _source: { title: 'doc 6' } }, { _source: { title: 'doc 7' } }, ] as unknown) as SavedObjectsRawDoc[]; - return expect( + await expect( bulkOverwriteTransformedDocuments(client, 'existing_index_with_write_block', newDocs)() ).rejects.toMatchObject(expect.anything()); }); diff --git a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts index 6dbb986e868ee..2baf27e94edb3 100644 --- a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts +++ b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts @@ -142,8 +142,8 @@ describe('migrationsStateActionMachine', () => { } `); }); - it('resolves when reaching the DONE state', () => { - return expect( + it('resolves when reaching the DONE state', async () => { + await expect( migrationStateActionMachine({ initialState, logger: mockLogger.get(), @@ -152,8 +152,8 @@ describe('migrationsStateActionMachine', () => { }) ).resolves.toEqual(expect.anything()); }); - it('resolves with migrated status if some sourceIndex in the DONE state', () => { - return expect( + it('resolves with migrated status if some sourceIndex in the DONE state', async () => { + await expect( migrationStateActionMachine({ initialState: { ...initialState, ...{ sourceIndex: Option.some('source-index') } }, logger: mockLogger.get(), @@ -162,8 +162,8 @@ describe('migrationsStateActionMachine', () => { }) ).resolves.toEqual(expect.objectContaining({ status: 'migrated' })); }); - it('resolves with patched status if none sourceIndex in the DONE state', () => { - return expect( + it('resolves with patched status if none sourceIndex in the DONE state', async () => { + await expect( migrationStateActionMachine({ initialState: { ...initialState, ...{ sourceIndex: Option.none } }, logger: mockLogger.get(), @@ -172,8 +172,8 @@ describe('migrationsStateActionMachine', () => { }) ).resolves.toEqual(expect.objectContaining({ status: 'patched' })); }); - it('rejects with error message when reaching the FATAL state', () => { - return expect( + it('rejects with error message when reaching the FATAL state', async () => { + await expect( migrationStateActionMachine({ initialState: { ...initialState, reason: 'the fatal reason' } as State, logger: mockLogger.get(), diff --git a/src/core/server/saved_objects/migrationsv2/state_action_machine.test.ts b/src/core/server/saved_objects/migrationsv2/state_action_machine.test.ts index 15dde10eb21ec..3760b92591346 100644 --- a/src/core/server/saved_objects/migrationsv2/state_action_machine.test.ts +++ b/src/core/server/saved_objects/migrationsv2/state_action_machine.test.ts @@ -86,14 +86,14 @@ describe('state action machine', () => { }); }); - test('rejects if an exception is throw from inside an action', () => { - return expect( + test('rejects if an exception is throw from inside an action', async () => { + await expect( stateActionMachine({ ...state, controlState: 'THROW' }, next, countUntilThree) ).rejects.toThrowErrorMatchingInlineSnapshot(`"Invalid control state"`); }); - test('resolve with the final state once all steps are completed', () => { - return expect(finalStateP).resolves.toMatchInlineSnapshot(` + test('resolve with the final state once all steps are completed', async () => { + await expect(finalStateP).resolves.toMatchInlineSnapshot(` Object { "controlState": "DONE", "count": 3, @@ -101,8 +101,8 @@ describe('state action machine', () => { `); }); - test("rejects if control state doesn't change after 50 steps", () => { - return expect( + test("rejects if control state doesn't change after 50 steps", async () => { + await expect( stateActionMachine(state, next, countUntilModel(51)) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Control state didn't change after 50 steps aborting."` diff --git a/src/core/server/saved_objects/service/lib/errors.ts b/src/core/server/saved_objects/service/lib/errors.ts index e8836dbd8f7a1..c6c8eee003e4e 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 5f07a4b523056..cef5f33726ed5 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/charts/public/services/legacy_colors/colors.test.ts b/src/plugins/charts/public/services/legacy_colors/colors.test.ts index 89cf7a4817377..b94918426c525 100644 --- a/src/plugins/charts/public/services/legacy_colors/colors.test.ts +++ b/src/plugins/charts/public/services/legacy_colors/colors.test.ts @@ -62,14 +62,17 @@ describe('Vislib Color Service', () => { it('should throw an error if input is not an array', () => { expect(() => { + // @ts-expect-error colors.createColorLookupFunction(200); }).toThrowError(); expect(() => { + // @ts-expect-error colors.createColorLookupFunction('help'); }).toThrowError(); expect(() => { + // @ts-expect-error colors.createColorLookupFunction(true); }).toThrowError(); @@ -78,10 +81,12 @@ describe('Vislib Color Service', () => { }).toThrowError(); expect(() => { + // @ts-expect-error colors.createColorLookupFunction(nullValue); }).toThrowError(); expect(() => { + // @ts-expect-error colors.createColorLookupFunction(emptyObject); }).toThrowError(); }); @@ -89,14 +94,17 @@ describe('Vislib Color Service', () => { describe('when array is not composed of numbers, strings, or undefined values', () => { it('should throw an error', () => { expect(() => { + // @ts-expect-error colors.createColorLookupFunction(arrayOfObjects); }).toThrowError(); expect(() => { + // @ts-expect-error colors.createColorLookupFunction(arrayOfBooleans); }).toThrowError(); expect(() => { + // @ts-expect-error colors.createColorLookupFunction(arrayOfNullValues); }).toThrowError(); }); @@ -113,6 +121,7 @@ describe('Vislib Color Service', () => { }).not.toThrowError(); expect(() => { + // @ts-expect-error colors.createColorLookupFunction(arrayOfUndefinedValues); }).not.toThrowError(); }); diff --git a/src/plugins/charts/public/services/legacy_colors/colors.ts b/src/plugins/charts/public/services/legacy_colors/colors.ts index e1342a114f8df..e367a780fb292 100644 --- a/src/plugins/charts/public/services/legacy_colors/colors.ts +++ b/src/plugins/charts/public/services/legacy_colors/colors.ts @@ -48,7 +48,7 @@ export class LegacyColorsService { } createColorLookupFunction( - arrayOfStringsOrNumbers?: any, + arrayOfStringsOrNumbers?: Array, colorMapping: Partial> = {} ) { if (!Array.isArray(arrayOfStringsOrNumbers)) { @@ -67,7 +67,7 @@ export class LegacyColorsService { this.mappedColors.mapKeys(arrayOfStringsOrNumbers); - return (value: string) => { + return (value: string | number) => { return colorMapping[value] || this.mappedColors.get(value); }; } diff --git a/src/plugins/charts/public/static/components/basic_options.tsx b/src/plugins/charts/public/static/components/basic_options.tsx index cac4c8d70d796..9c5a22543df99 100644 --- a/src/plugins/charts/public/static/components/basic_options.tsx +++ b/src/plugins/charts/public/static/components/basic_options.tsx @@ -18,9 +18,11 @@ */ import React from 'react'; + import { i18n } from '@kbn/i18n'; -import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; +import { VisOptionsProps } from '../../../../vis_default_editor/public'; + import { SwitchOption } from './switch'; import { SelectOption } from './select'; diff --git a/src/plugins/charts/public/static/components/collections.ts b/src/plugins/charts/public/static/components/collections.ts index 9dde50f2b44c9..16d875836a202 100644 --- a/src/plugins/charts/public/static/components/collections.ts +++ b/src/plugins/charts/public/static/components/collections.ts @@ -18,17 +18,22 @@ */ import { $Values } from '@kbn/utility-types'; +import { i18n } from '@kbn/i18n'; -export const ColorModes = Object.freeze({ - BACKGROUND: 'Background' as 'Background', - LABELS: 'Labels' as 'Labels', - NONE: 'None' as 'None', +export const ColorMode = Object.freeze({ + Background: 'Background' as 'Background', + Labels: 'Labels' as 'Labels', + None: 'None' as 'None', }); -export type ColorModes = $Values; +export type ColorMode = $Values; -export const Rotates = Object.freeze({ - HORIZONTAL: 0, - VERTICAL: 90, - ANGLED: 75, +export const LabelRotation = Object.freeze({ + Horizontal: 0, + Vertical: 90, + Angled: 75, +}); +export type LabelRotation = $Values; + +export const defaultCountLabel = i18n.translate('charts.countText', { + defaultMessage: 'Count', }); -export type Rotates = $Values; diff --git a/src/plugins/charts/public/static/components/color_picker.scss b/src/plugins/charts/public/static/components/color_picker.scss new file mode 100644 index 0000000000000..85bfefca41a09 --- /dev/null +++ b/src/plugins/charts/public/static/components/color_picker.scss @@ -0,0 +1,18 @@ +$visColorPickerWidth: $euiSizeL * 8; // 8 columns + +.visColorPicker__value { + width: $visColorPickerWidth; +} + +.visColorPicker__valueDot { + cursor: pointer; + + &:hover { + transform: scale(1.4); + } + + &-isSelected { + border: $euiSizeXS solid; + border-radius: 100%; + } +} diff --git a/src/plugins/charts/public/static/components/color_picker.tsx b/src/plugins/charts/public/static/components/color_picker.tsx new file mode 100644 index 0000000000000..d785a9c9ad4b7 --- /dev/null +++ b/src/plugins/charts/public/static/components/color_picker.tsx @@ -0,0 +1,138 @@ +/* + * 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 classNames from 'classnames'; +import React, { BaseSyntheticEvent } from 'react'; + +import { EuiButtonEmpty, EuiFlexItem, EuiIcon } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import './color_picker.scss'; + +export const legendColors: string[] = [ + '#3F6833', + '#967302', + '#2F575E', + '#99440A', + '#58140C', + '#052B51', + '#511749', + '#3F2B5B', + '#508642', + '#CCA300', + '#447EBC', + '#C15C17', + '#890F02', + '#0A437C', + '#6D1F62', + '#584477', + '#629E51', + '#E5AC0E', + '#64B0C8', + '#E0752D', + '#BF1B00', + '#0A50A1', + '#962D82', + '#614D93', + '#7EB26D', + '#EAB839', + '#6ED0E0', + '#EF843C', + '#E24D42', + '#1F78C1', + '#BA43A9', + '#705DA0', + '#9AC48A', + '#F2C96D', + '#65C5DB', + '#F9934E', + '#EA6460', + '#5195CE', + '#D683CE', + '#806EB7', + '#B7DBAB', + '#F4D598', + '#70DBED', + '#F9BA8F', + '#F29191', + '#82B5D8', + '#E5A8E2', + '#AEA2E0', + '#E0F9D7', + '#FCEACA', + '#CFFAFF', + '#F9E2D2', + '#FCE2DE', + '#BADFF4', + '#F9D9F9', + '#DEDAF7', +]; + +interface ColorPickerProps { + id?: string; + label: string | number | null; + onChange: (color: string | null, event: BaseSyntheticEvent) => void; + color: string; +} + +export const ColorPicker = ({ onChange, color: selectedColor, id, label }: ColorPickerProps) => ( +
+ + + +
+ {legendColors.map((color) => ( + onChange(color, e)} + onKeyPress={(e) => onChange(color, e)} + className={classNames('visColorPicker__valueDot', { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'visColorPicker__valueDot-isSelected': color === selectedColor, + })} + style={{ color }} + data-test-subj={`visColorPickerColor-${color}`} + /> + ))} +
+ {legendColors.some((c) => c === selectedColor) && ( + + onChange(null, e)} + onKeyPress={(e: any) => onChange(null, e)} + > + + + + )} +
+); diff --git a/src/plugins/charts/public/static/components/current_time.tsx b/src/plugins/charts/public/static/components/current_time.tsx new file mode 100644 index 0000000000000..00c1a74a7bfd8 --- /dev/null +++ b/src/plugins/charts/public/static/components/current_time.tsx @@ -0,0 +1,64 @@ +/* + * 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 moment, { Moment } from 'moment'; +import React, { FC } from 'react'; + +import { LineAnnotation, AnnotationDomainTypes, LineAnnotationStyle } from '@elastic/charts'; +import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json'; +import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json'; + +interface CurrentTimeProps { + isDarkMode: boolean; + domainEnd?: number | Moment; +} + +/** + * Render current time line annotation on @elastic/charts `Chart` + */ +export const CurrentTime: FC = ({ isDarkMode, domainEnd }) => { + const lineAnnotationStyle: Partial = { + line: { + strokeWidth: 2, + stroke: isDarkMode ? darkEuiTheme.euiColorDanger : lightEuiTheme.euiColorDanger, + opacity: 0.7, + }, + }; + + // Domain end of 'now' will be milliseconds behind current time, so we extend time by 1 minute and check if + // the annotation is within this range; if so, the line annotation uses the domainEnd as its value + const now = moment(); + const isAnnotationAtEdge = domainEnd + ? moment(domainEnd).add(1, 'm').isAfter(now) && now.isAfter(domainEnd) + : false; + const lineAnnotationData = [ + { + dataValue: isAnnotationAtEdge ? domainEnd : now.valueOf(), + }, + ]; + + return ( + + ); +}; diff --git a/src/plugins/charts/public/static/components/endzones.tsx b/src/plugins/charts/public/static/components/endzones.tsx new file mode 100644 index 0000000000000..db68f72c18c05 --- /dev/null +++ b/src/plugins/charts/public/static/components/endzones.tsx @@ -0,0 +1,197 @@ +/* + * 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 React, { FC } from 'react'; +import moment, { unitOfTime } from 'moment'; + +import { + TooltipValue, + RectAnnotation, + RectAnnotationDatum, + RectAnnotationStyle, +} from '@elastic/charts'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui'; +import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json'; +import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json'; + +interface EndzonesProps { + isDarkMode: boolean; + domainStart: number; + domainEnd: number; + interval: number; + domainMin: number; + domainMax: number; + hideTooltips?: boolean; + /** + * used to toggle full bin endzones for multiple non-stacked bars + */ + isFullBin?: boolean; +} + +export const Endzones: FC = ({ + isDarkMode, + domainStart, + domainEnd, + interval, + domainMin, + domainMax, + hideTooltips = true, + isFullBin = false, +}) => { + const rectAnnotationStyle: Partial = { + stroke: isDarkMode ? darkEuiTheme.euiColorLightShade : lightEuiTheme.euiColorDarkShade, + strokeWidth: 0, + opacity: isDarkMode ? 0.6 : 0.2, + fill: isDarkMode ? darkEuiTheme.euiColorLightShade : lightEuiTheme.euiColorDarkShade, + }; + + const rectAnnotations: RectAnnotationDatum[] = []; + + if (domainStart > domainMin) { + rectAnnotations.push({ + coordinates: { + x1: isFullBin ? domainMin : domainStart, + }, + }); + } + + if (domainEnd - interval < domainMax) { + rectAnnotations.push({ + coordinates: { + x0: isFullBin ? domainMax : domainEnd, + }, + }); + } + + return ( + + ); +}; + +const findIntervalFromDuration = ( + dateValue: number, + esValue: number, + esUnit: unitOfTime.Base, + timeZone: string +) => { + const date = moment.tz(dateValue, timeZone); + const startOfDate = moment.tz(date, timeZone).startOf(esUnit); + const endOfDate = moment.tz(date, timeZone).startOf(esUnit).add(esValue, esUnit); + return endOfDate.valueOf() - startOfDate.valueOf(); +}; + +const getIntervalInMs = ( + value: number, + esValue: number, + esUnit: unitOfTime.Base, + timeZone: string +): number => { + switch (esUnit) { + case 's': + return 1000 * esValue; + case 'ms': + return 1 * esValue; + default: + return findIntervalFromDuration(value, esValue, esUnit, timeZone); + } +}; + +/** + * Returns the adjusted interval based on the data + * + * @param xValues sorted and unquie x values + * @param esValue + * @param esUnit + * @param timeZone + */ +export const getAdjustedInterval = ( + xValues: number[], + esValue: number, + esUnit: unitOfTime.Base, + timeZone: string +): number => { + const newInterval = xValues.reduce((minInterval, currentXvalue, index) => { + let currentDiff = minInterval; + + if (index > 0) { + currentDiff = Math.abs(xValues[index - 1] - currentXvalue); + } + + const singleUnitInterval = getIntervalInMs(currentXvalue, esValue, esUnit, timeZone); + return Math.min(minInterval, singleUnitInterval, currentDiff); + }, Number.MAX_SAFE_INTEGER); + + return newInterval > 0 ? newInterval : moment.duration(esValue, esUnit).asMilliseconds(); +}; + +const partialDataText = i18n.translate('charts.partialData.bucketTooltipText', { + defaultMessage: + 'The selected time range does not include this entire bucket. It might contain partial data.', +}); + +const Prompt = () => ( + + + + + {partialDataText} + +); + +export const renderEndzoneTooltip = ( + xInterval?: number, + domainStart?: number, + domainEnd?: number, + formatter?: (v: any) => string, + renderValue = true +) => (headerData: TooltipValue): JSX.Element | string => { + const headerDataValue = headerData.value; + const formattedValue = formatter ? formatter(headerDataValue) : headerDataValue; + + if ( + (domainStart !== undefined && domainStart > headerDataValue) || + (domainEnd !== undefined && xInterval !== undefined && domainEnd - xInterval < headerDataValue) + ) { + return ( + <> + + {renderValue && ( + <> + +

{formattedValue}

+ + )} + + ); + } + + return renderValue ? formattedValue : null; +}; diff --git a/src/plugins/charts/public/static/components/index.ts b/src/plugins/charts/public/static/components/index.ts index 48c9e40145481..c044d361bed18 100644 --- a/src/plugins/charts/public/static/components/index.ts +++ b/src/plugins/charts/public/static/components/index.ts @@ -18,7 +18,7 @@ */ export { BasicOptions } from './basic_options'; -export { ColorModes, Rotates } from './collections'; +export { ColorMode, LabelRotation, defaultCountLabel } from './collections'; export { ColorRanges, SetColorRangeValue } from './color_ranges'; export { ColorSchemaOptions, SetColorSchemaOptionsValue } from './color_schema'; export { ColorSchemaParams, Labels, Style } from './types'; @@ -28,3 +28,7 @@ export { RequiredNumberInputOption } from './required_number_input'; export { SelectOption } from './select'; export { SwitchOption } from './switch'; export { TextInputOption } from './text_input'; +export { LegendToggle } from './legend_toggle'; +export { ColorPicker } from './color_picker'; +export { CurrentTime } from './current_time'; +export * from './endzones'; diff --git a/src/plugins/charts/public/static/components/legend_toggle.scss b/src/plugins/charts/public/static/components/legend_toggle.scss new file mode 100644 index 0000000000000..7eb85a5e08ec0 --- /dev/null +++ b/src/plugins/charts/public/static/components/legend_toggle.scss @@ -0,0 +1,20 @@ +.echLegend__toggle { + position: absolute; + bottom: 0; + left: 0; + z-index: 1; + margin: $euiSizeXS; + + &--isOpen { + background-color: $euiColorLightestShade; + } + + &--position-left, + &--position-bottom { + left: auto; + bottom: auto; + right: 0; + top: 0; + } +} + diff --git a/src/plugins/charts/public/static/components/legend_toggle.tsx b/src/plugins/charts/public/static/components/legend_toggle.tsx new file mode 100644 index 0000000000000..12742b6da6e6b --- /dev/null +++ b/src/plugins/charts/public/static/components/legend_toggle.tsx @@ -0,0 +1,62 @@ +/* + * 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 React, { memo, useMemo } from 'react'; +import classNames from 'classnames'; + +import { i18n } from '@kbn/i18n'; +import { htmlIdGenerator, EuiButtonIcon } from '@elastic/eui'; +import { Position } from '@elastic/charts'; + +import './legend_toggle.scss'; + +interface LegendToggleProps { + onClick: () => void; + showLegend: boolean; + legendPosition: Position; +} + +const LegendToggleComponent = ({ onClick, showLegend, legendPosition }: LegendToggleProps) => { + const legendId = useMemo(() => htmlIdGenerator()('legend'), []); + + return ( + + ); +}; + +export const LegendToggle = memo(LegendToggleComponent); diff --git a/src/plugins/charts/public/static/components/types.ts b/src/plugins/charts/public/static/components/types.ts index 196eb60b06aec..a4c384141dafb 100644 --- a/src/plugins/charts/public/static/components/types.ts +++ b/src/plugins/charts/public/static/components/types.ts @@ -18,7 +18,7 @@ */ import { ColorSchemas } from '../color_maps'; -import { Rotates } from './collections'; +import { LabelRotation } from './collections'; export interface ColorSchemaParams { colorSchema: ColorSchemas; @@ -29,8 +29,8 @@ export interface Labels { color?: string; filter?: boolean; overwriteColor?: boolean; - rotate?: Rotates; - show: boolean; + rotate?: LabelRotation; + show?: boolean; truncate?: number | null; } diff --git a/src/plugins/charts/public/static/index.ts b/src/plugins/charts/public/static/index.ts index b8a8406c375dd..638e119d8be49 100644 --- a/src/plugins/charts/public/static/index.ts +++ b/src/plugins/charts/public/static/index.ts @@ -20,3 +20,4 @@ export * from './color_maps'; export * from './colors'; export * from './components'; +export * from './utils'; diff --git a/src/plugins/charts/public/static/utils/index.ts b/src/plugins/charts/public/static/utils/index.ts new file mode 100644 index 0000000000000..777deb326125e --- /dev/null +++ b/src/plugins/charts/public/static/utils/index.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export * from './transform_click_event'; diff --git a/src/plugins/charts/public/static/utils/transform_click_event.ts b/src/plugins/charts/public/static/utils/transform_click_event.ts new file mode 100644 index 0000000000000..21460eb51e3fb --- /dev/null +++ b/src/plugins/charts/public/static/utils/transform_click_event.ts @@ -0,0 +1,238 @@ +/* + * 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 { + XYChartSeriesIdentifier, + GeometryValue, + XYBrushArea, + Accessor, + AccessorFn, + Datum, +} from '@elastic/charts'; + +import { RangeSelectContext, ValueClickContext } from '../../../../embeddable/public'; +import { Datatable } from '../../../../expressions/public'; + +export interface ClickTriggerEvent { + name: 'filterBucket'; + data: ValueClickContext['data']; +} + +export interface BrushTriggerEvent { + name: 'brush'; + data: RangeSelectContext['data']; +} + +type AllSeriesAccessors = Array<[accessor: Accessor | AccessorFn, value: string | number]>; + +/** + * returns accessor value from string or function accessor + * @param datum + * @param accessor + */ +function getAccessorValue(datum: Datum, accessor: Accessor | AccessorFn) { + if (typeof accessor === 'function') { + return accessor(datum); + } + + return datum[accessor]; +} + +/** + * This is a little unorthodox, but using functional accessors makes it + * difficult to match the correct column. This creates a test object to throw + * an error when the target id is accessed, thus matcing the target column. + */ +function validateAccessorId(id: string, accessor: Accessor | AccessorFn) { + if (typeof accessor !== 'function') { + return id === accessor; + } + + const matchedMessage = 'validateAccessorId matched'; + + try { + accessor({ + get [id]() { + throw new Error(matchedMessage); + }, + }); + return false; + } catch ({ message }) { + return message === matchedMessage; + } +} + +/** + * Groups split accessors by their accessor string or function and related value + * + * @param splitAccessors + * @param splitSeriesAccessorFnMap + */ +const getAllSplitAccessors = ( + splitAccessors: Map, + splitSeriesAccessorFnMap?: Map +): Array<[accessor: Accessor | AccessorFn, value: string | number]> => + [...splitAccessors.entries()].map(([key, value]) => [ + splitSeriesAccessorFnMap?.get?.(key) ?? key, + value, + ]); + +/** + * Reduces matching column indexes + * + * @param xAccessor + * @param yAccessor + * @param splitAccessors + */ +const columnReducer = ( + xAccessor: Accessor | AccessorFn | null, + yAccessor: Accessor | AccessorFn | null, + splitAccessors: AllSeriesAccessors +) => ( + acc: Array<[index: number, id: string]>, + { id }: Datatable['columns'][number], + index: number +): Array<[index: number, id: string]> => { + if ( + (xAccessor !== null && validateAccessorId(id, xAccessor)) || + (yAccessor !== null && validateAccessorId(id, yAccessor)) || + splitAccessors.some(([accessor]) => validateAccessorId(id, accessor)) + ) { + acc.push([index, id]); + } + + return acc; +}; + +/** + * Finds matching row index for given accessors and geometry values + * + * @param geometry + * @param xAccessor + * @param yAccessor + * @param splitAccessors + */ +const rowFindPredicate = ( + geometry: GeometryValue | null, + xAccessor: Accessor | AccessorFn | null, + yAccessor: Accessor | AccessorFn | null, + splitAccessors: AllSeriesAccessors +) => (row: Datatable['rows'][number]): boolean => + (geometry === null || + (xAccessor !== null && + getAccessorValue(row, xAccessor) === geometry.x && + yAccessor !== null && + getAccessorValue(row, yAccessor) === geometry.y)) && + [...splitAccessors].every(([accessor, value]) => getAccessorValue(row, accessor) === value); + +/** + * Helper function to transform `@elastic/charts` click event into filter action event + * + * @param table + * @param xAccessor + * @param splitSeriesAccessorFnMap needed when using `splitSeriesAccessors` as `AccessorFn` + * @param negate + */ +export const getFilterFromChartClickEventFn = ( + table: Datatable, + xAccessor: Accessor | AccessorFn, + splitSeriesAccessorFnMap?: Map, + negate: boolean = false +) => (points: Array<[GeometryValue, XYChartSeriesIdentifier]>): ClickTriggerEvent => { + const data: ValueClickContext['data']['data'] = []; + + points.forEach((point) => { + const [geometry, { yAccessor, splitAccessors }] = point; + const allSplitAccessors = getAllSplitAccessors(splitAccessors, splitSeriesAccessorFnMap); + const columns = table.columns.reduce>( + columnReducer(xAccessor, yAccessor, allSplitAccessors), + [] + ); + const row = table.rows.findIndex( + rowFindPredicate(geometry, xAccessor, yAccessor, allSplitAccessors) + ); + const newData = columns.map(([column, id]) => ({ + table, + column, + row, + value: table.rows?.[row]?.[id] ?? null, + })); + + data.push(...newData); + }); + + return { + name: 'filterBucket', + data: { + negate, + data, + }, + }; +}; + +/** + * Helper function to get filter action event from series + */ +export const getFilterFromSeriesFn = (table: Datatable) => ( + { splitAccessors }: XYChartSeriesIdentifier, + splitSeriesAccessorFnMap?: Map, + negate = false +): ClickTriggerEvent => { + const allSplitAccessors = getAllSplitAccessors(splitAccessors, splitSeriesAccessorFnMap); + const columns = table.columns.reduce>( + columnReducer(null, null, allSplitAccessors), + [] + ); + const row = table.rows.findIndex(rowFindPredicate(null, null, null, allSplitAccessors)); + const data: ValueClickContext['data']['data'] = columns.map(([column, id]) => ({ + table, + column, + row, + value: table.rows?.[row]?.[id] ?? null, + })); + + return { + name: 'filterBucket', + data: { + negate, + data, + }, + }; +}; + +/** + * Helper function to transform `@elastic/charts` brush event into brush action event + */ +export const getBrushFromChartBrushEventFn = ( + table: Datatable, + xAccessor: Accessor | AccessorFn +) => ({ x: selectedRange }: XYBrushArea): BrushTriggerEvent => { + const [start, end] = selectedRange ?? [0, 0]; + const range: [number, number] = [start, end]; + const column = table.columns.findIndex(({ id }) => validateAccessorId(id, xAccessor)); + + return { + data: { + table, + column, + range, + }, + name: 'brush', + }; +}; diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index 8eff48251b371..845d64c16500d 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 937e6737d2716..915f245fbcd19 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_pattern.stub.ts b/src/plugins/data/common/index_patterns/index_pattern.stub.ts index e7384e09494aa..c47a8b605ada3 100644 --- a/src/plugins/data/common/index_patterns/index_pattern.stub.ts +++ b/src/plugins/data/common/index_patterns/index_pattern.stub.ts @@ -25,6 +25,7 @@ export const stubIndexPattern: IIndexPattern = { fields: stubFields, title: 'logstash-*', timeFieldName: '@timestamp', + getTimeField: () => ({ name: '@timestamp', type: 'date' }), }; export const stubIndexPatternWithFields: IIndexPattern = { 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 19ec286307a09..76de2b2662bb0 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 c020e7595c565..bad74430b8966 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 4c89cbeb446a0..590ff872f3bf9 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 2a203b57d201b..3d32742c168ad 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 0235f748ec1e0..3333dba36fe69 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 8d9b29175162e..12496b07d3482 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/aggs/buckets/date_histogram.ts b/src/plugins/data/common/search/aggs/buckets/date_histogram.ts index ba79a4264d603..6b7f1522d19ad 100644 --- a/src/plugins/data/common/search/aggs/buckets/date_histogram.ts +++ b/src/plugins/data/common/search/aggs/buckets/date_histogram.ts @@ -149,7 +149,7 @@ export const getDateHistogramBucketAgg = ({ type: 'field', filterFieldTypes: KBN_FIELD_TYPES.DATE, default(agg: IBucketDateHistogramAggConfig) { - return agg.getIndexPattern().timeFieldName; + return agg.getIndexPattern().getTimeField?.()?.name; }, onChange(agg: IBucketDateHistogramAggConfig) { if (isAutoInterval(get(agg, 'params.interval')) && !agg.fieldIsTimeField()) { 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 78d169e8529c5..9d0c63a8dc7aa 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 e4385526ee6e8..b773aad67c3f8 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 944da72bd11d1..84ce5b0382624 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 2d7aeff79a689..2b0911b72abd5 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 23d2ab080d75e..04801a5ee1cea 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 ce7ecf434056a..30fef7e3a7c66 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 1781da980dc30..3b84523d782f6 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 81e62380eacfb..8f207e94e8fbe 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 3dda04d738c96..7b15e2576e704 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 ca0f35d6612b2..36a193a4f6f94 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 eb3a053b78a2d..c60a1efabf987 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 e5df6d860b404..3493844a71ac1 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 aa54706476a8f..816c1737608da 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/data/public/triggers/index.ts b/src/plugins/data/public/triggers/index.ts new file mode 100644 index 0000000000000..36a38ae76bc0e --- /dev/null +++ b/src/plugins/data/public/triggers/index.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +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 f4d1a8988da78..f842568859fc2 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 21a3bf6e73e61..9023044184df3 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 e9dbc2e972c68..f0b51e456337f 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 57a745b19748d..1163fd2dc9953 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 10567544af6ea..8bd59e47730fd 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/kql_telemetry/usage_collector/fetch.test.ts b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts index 1794df7391cb0..038f340babb1f 100644 --- a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts +++ b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts @@ -18,7 +18,7 @@ */ import { fetchProvider } from './fetch'; -import { LegacyAPICaller } from 'kibana/server'; +import { ElasticsearchClient } from 'kibana/server'; import { CollectorFetchContext } from 'src/plugins/usage_collection/server'; import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks'; @@ -30,7 +30,7 @@ jest.mock('../../../common', () => ({ })); let fetch: ReturnType; -let callCluster: LegacyAPICaller; +let esClient: ElasticsearchClient; let collectorFetchContext: CollectorFetchContext; const collectorFetchContextMock = createCollectorFetchContextMock(); @@ -38,34 +38,33 @@ function setupMockCallCluster( optCount: { optInCount?: number; optOutCount?: number } | null, language: string | undefined | null ) { - callCluster = (jest.fn((method, params) => { - if (params && 'id' in params && params.id === 'kql-telemetry:kql-telemetry') { - if (optCount === null) { - return Promise.resolve({ + function mockedEsGetMethod() { + if (optCount === null) { + return Promise.resolve({ + body: { _index: '.kibana_1', _id: 'kql-telemetry:kql-telemetry', found: false, - }); - } else { - return Promise.resolve({ + }, + }); + } else { + return Promise.resolve({ + body: { _source: { - 'kql-telemetry': { - ...optCount, - }, + 'kql-telemetry': { ...optCount }, type: 'kql-telemetry', updated_at: '2018-10-05T20:20:56.258Z', }, - }); - } - } else if (params && 'body' in params && params.body.query.term.type === 'config') { - if (language === 'missingConfigDoc') { - return Promise.resolve({ - hits: { - hits: [], - }, - }); - } else { - return Promise.resolve({ + }, + }); + } + } + function mockedEsSearchMethod() { + if (language === 'missingConfigDoc') { + return Promise.resolve({ body: { hits: { hits: [] } } }); + } else { + return Promise.resolve({ + body: { hits: { hits: [ { @@ -77,12 +76,15 @@ function setupMockCallCluster( }, ], }, - }); - } + }, + }); } - - throw new Error('invalid call'); - }) as unknown) as LegacyAPICaller; + } + const esClientMock = ({ + get: jest.fn().mockImplementation(mockedEsGetMethod), + search: jest.fn().mockImplementation(mockedEsSearchMethod), + } as unknown) as ElasticsearchClient; + esClient = esClientMock; } describe('makeKQLUsageCollector', () => { @@ -95,7 +97,7 @@ describe('makeKQLUsageCollector', () => { setupMockCallCluster({ optInCount: 1 }, 'kuery'); collectorFetchContext = { ...collectorFetchContextMock, - callCluster, + esClient, }; const fetchResponse = await fetch(collectorFetchContext); expect(fetchResponse.optInCount).toBe(1); @@ -106,7 +108,7 @@ describe('makeKQLUsageCollector', () => { setupMockCallCluster({ optInCount: 1 }, 'kuery'); collectorFetchContext = { ...collectorFetchContextMock, - callCluster, + esClient, }; const fetchResponse = await fetch(collectorFetchContext); expect(fetchResponse.defaultQueryLanguage).toBe('kuery'); @@ -117,7 +119,7 @@ describe('makeKQLUsageCollector', () => { setupMockCallCluster({ optInCount: 1 }, null); collectorFetchContext = { ...collectorFetchContextMock, - callCluster, + esClient, }; const fetchResponse = await fetch(collectorFetchContext); expect(fetchResponse.defaultQueryLanguage).toBe('lucene'); @@ -127,7 +129,7 @@ describe('makeKQLUsageCollector', () => { setupMockCallCluster({ optInCount: 1 }, undefined); collectorFetchContext = { ...collectorFetchContextMock, - callCluster, + esClient, }; const fetchResponse = await fetch(collectorFetchContext); expect(fetchResponse.defaultQueryLanguage).toBe('default-lucene'); @@ -137,7 +139,7 @@ describe('makeKQLUsageCollector', () => { setupMockCallCluster(null, 'kuery'); collectorFetchContext = { ...collectorFetchContextMock, - callCluster, + esClient, }; const fetchResponse = await fetch(collectorFetchContext); expect(fetchResponse.optInCount).toBe(0); @@ -148,7 +150,7 @@ describe('makeKQLUsageCollector', () => { setupMockCallCluster(null, 'missingConfigDoc'); collectorFetchContext = { ...collectorFetchContextMock, - callCluster, + esClient, }; const fetchResponse = await fetch(collectorFetchContext); expect(fetchResponse.defaultQueryLanguage).toBe('default-lucene'); diff --git a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts index 21a1843d1ec81..5178aa65705d8 100644 --- a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts +++ b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts @@ -30,18 +30,22 @@ export interface Usage { } export function fetchProvider(index: string) { - return async ({ callCluster }: CollectorFetchContext): Promise => { - const [response, config] = await Promise.all([ - callCluster('get', { - index, - id: 'kql-telemetry:kql-telemetry', - ignore: [404], - }), - callCluster('search', { - index, - body: { query: { term: { type: 'config' } } }, - ignore: [404], - }), + return async ({ esClient }: CollectorFetchContext): Promise => { + const [{ body: response }, { body: config }] = await Promise.all([ + esClient.get( + { + index, + id: 'kql-telemetry:kql-telemetry', + }, + { ignore: [404] } + ), + esClient.search( + { + index, + body: { query: { term: { type: 'config' } } }, + }, + { ignore: [404] } + ), ]); const queryLanguageConfigValue: string | null | undefined = get( 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 b1410e2498667..3b223e6fdb9b2 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 768041a376ad1..4650aeefba056 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/search/collectors/fetch.ts b/src/plugins/data/server/search/collectors/fetch.ts index 344bc18c7b4b6..9d0d431cf4eaf 100644 --- a/src/plugins/data/server/search/collectors/fetch.ts +++ b/src/plugins/data/server/search/collectors/fetch.ts @@ -20,31 +20,34 @@ import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; import { SharedGlobalConfig } from 'kibana/server'; +import { SearchResponse } from 'elasticsearch'; import { CollectorFetchContext } from 'src/plugins/usage_collection/server'; import { Usage } from './register'; - -interface SearchTelemetrySavedObject { +interface SearchTelemetry { 'search-telemetry': Usage; } +type ESResponse = SearchResponse; export function fetchProvider(config$: Observable) { - return async ({ callCluster }: CollectorFetchContext): Promise => { + return async ({ esClient }: CollectorFetchContext): Promise => { const config = await config$.pipe(first()).toPromise(); - - const response = await callCluster('search', { - index: config.kibana.index, - body: { - query: { term: { type: { value: 'search-telemetry' } } }, + const { body: esResponse } = await esClient.search( + { + index: config.kibana.index, + body: { + query: { term: { type: { value: 'search-telemetry' } } }, + }, }, - ignore: [404], - }); - - return response.hits.hits.length - ? response.hits.hits[0]._source['search-telemetry'] - : { - successCount: 0, - errorCount: 0, - averageDuration: null, - }; + { ignore: [404] } + ); + const size = esResponse?.hits?.hits?.length ?? 0; + if (!size) { + return { + successCount: 0, + errorCount: 0, + averageDuration: null, + }; + } + return esResponse.hits.hits[0]._source['search-telemetry']; }; } diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index 620df9c8edcb0..983e4a44370d9 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -25,6 +25,7 @@ import type { SearchUsage } from '../collectors'; import { getDefaultSearchParams, getShardTimeout, shimAbortSignal } from './request_utils'; import { toKibanaSearchResponse } from './response_utils'; import { searchUsageObserver } from '../collectors/usage'; +import { KbnServerError } from '../../../../kibana_utils/server'; export const esSearchStrategyProvider = ( config$: Observable, @@ -35,7 +36,7 @@ export const esSearchStrategyProvider = ( // Only default index pattern type is supported here. // See data_enhanced for other type support. if (request.indexType) { - throw new Error(`Unsupported index pattern type ${request.indexType}`); + throw new KbnServerError(`Unsupported index pattern type ${request.indexType}`, 400); } const search = async () => { diff --git a/src/plugins/data/server/search/routes/msearch.ts b/src/plugins/data/server/search/routes/msearch.ts index 7b44aa18bf8fc..ae6362fac1b21 100644 --- a/src/plugins/data/server/search/routes/msearch.ts +++ b/src/plugins/data/server/search/routes/msearch.ts @@ -23,6 +23,7 @@ import { IRouter } from 'src/core/server'; import { SearchRouteDependencies } from '../search_service'; import { getCallMsearch } from './call_msearch'; +import { reportServerError } from '../../../../kibana_utils/server'; /** * The msearch route takes in an array of searches, each consisting of header @@ -69,15 +70,7 @@ export function registerMsearchRoute(router: IRouter, deps: SearchRouteDependenc const response = await callMsearch({ body: request.body }); return res.ok(response); } catch (err) { - return res.customError({ - statusCode: err.statusCode || 500, - body: { - message: err.message, - attributes: { - error: err.body?.error || err.message, - }, - }, - }); + return reportServerError(res, err); } } ); diff --git a/src/plugins/data/server/search/routes/search.ts b/src/plugins/data/server/search/routes/search.ts index ed519164c8e43..6c27b7e2c43d3 100644 --- a/src/plugins/data/server/search/routes/search.ts +++ b/src/plugins/data/server/search/routes/search.ts @@ -22,6 +22,7 @@ import { schema } from '@kbn/config-schema'; import type { IRouter } from 'src/core/server'; import { getRequestAbortedSignal } from '../../lib'; import { shimHitsTotal } from './shim_hits_total'; +import { reportServerError } from '../../../../kibana_utils/server'; export function registerSearchRoute(router: IRouter): void { router.post( @@ -74,15 +75,7 @@ export function registerSearchRoute(router: IRouter): void { }, }); } catch (err) { - return res.customError({ - statusCode: err.statusCode || 500, - body: { - message: err.message, - attributes: { - error: err.body?.error || err.message, - }, - }, - }); + return reportServerError(res, err); } } ); @@ -106,15 +99,7 @@ export function registerSearchRoute(router: IRouter): void { await context.search!.cancel(id, { strategy }); return res.ok(); } catch (err) { - return res.customError({ - statusCode: err.statusCode, - body: { - message: err.message, - attributes: { - error: err.body.error, - }, - }, - }); + return reportServerError(res, err); } } ); diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 6c8683220bc4c..5de019cd1b83e 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -73,6 +73,7 @@ import { import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn'; import { ConfigSchema } from '../../config'; import { SessionService, IScopedSessionService, ISessionService } from './session'; +import { KbnServerError } from '../../../kibana_utils/server'; declare module 'src/core/server' { interface RequestHandlerContext { @@ -305,7 +306,13 @@ export class SearchService implements Plugin { private cancel = (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => { const strategy = this.getSearchStrategy(options.strategy); - return strategy.cancel ? strategy.cancel(id, options, deps) : Promise.resolve(); + if (!strategy.cancel) { + throw new KbnServerError( + `Search strategy ${options.strategy} doesn't support cancellations`, + 400 + ); + } + return strategy.cancel(id, options, deps); }; private getSearchStrategy = < @@ -317,7 +324,7 @@ export class SearchService implements Plugin { this.logger.debug(`Get strategy ${name}`); const strategy = this.searchStrategies[name]; if (!strategy) { - throw new Error(`Search strategy ${name} not found`); + throw new KbnServerError(`Search strategy ${name} not found`, 404); } return strategy; }; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 4d24e6d1afd49..cd3527d5ad7ab 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/directives/histogram.tsx b/src/plugins/discover/public/application/angular/directives/histogram.tsx index 4c39c8bb25542..5a0bd5cca6109 100644 --- a/src/plugins/discover/public/application/angular/directives/histogram.tsx +++ b/src/plugins/discover/public/application/angular/directives/histogram.tsx @@ -17,25 +17,17 @@ * under the License. */ -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui'; -import moment from 'moment-timezone'; -import { unitOfTime } from 'moment'; +import moment, { unitOfTime } from 'moment-timezone'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json'; -import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json'; import { - AnnotationDomainTypes, Axis, Chart, HistogramBarSeries, - LineAnnotation, Position, ScaleType, Settings, - RectAnnotation, - TooltipValue, TooltipType, ElementClickListener, XYChartElementEvent, @@ -43,12 +35,17 @@ import { Theme, } from '@elastic/charts'; -import { i18n } from '@kbn/i18n'; import { IUiSettingsClient } from 'kibana/public'; import { EuiChartThemeType } from '@elastic/eui/dist/eui_charts_theme'; import { Subscription, combineLatest } from 'rxjs'; import { getServices } from '../../../kibana_services'; import { Chart as IChart } from '../helpers/point_series'; +import { + CurrentTime, + Endzones, + getAdjustedInterval, + renderEndzoneTooltip, +} from '../../../../../charts/public'; export interface DiscoverHistogramProps { chartData: IChart; @@ -60,34 +57,6 @@ interface DiscoverHistogramState { chartsBaseTheme: Theme; } -function findIntervalFromDuration( - dateValue: number, - esValue: number, - esUnit: unitOfTime.Base, - timeZone: string -) { - const date = moment.tz(dateValue, timeZone); - const startOfDate = moment.tz(date, timeZone).startOf(esUnit); - const endOfDate = moment.tz(date, timeZone).startOf(esUnit).add(esValue, esUnit); - return endOfDate.valueOf() - startOfDate.valueOf(); -} - -function getIntervalInMs( - value: number, - esValue: number, - esUnit: unitOfTime.Base, - timeZone: string -): number { - switch (esUnit) { - case 's': - return 1000 * esValue; - case 'ms': - return 1 * esValue; - default: - return findIntervalFromDuration(value, esValue, esUnit, timeZone); - } -} - function getTimezone(uiSettings: IUiSettingsClient) { if (uiSettings.isDefault('dateFormat:tz')) { const detectedTimezone = moment.tz.guess(); @@ -98,27 +67,6 @@ function getTimezone(uiSettings: IUiSettingsClient) { } } -export function findMinInterval( - xValues: number[], - esValue: number, - esUnit: string, - timeZone: string -): number { - return xValues.reduce((minInterval, currentXvalue, index) => { - let currentDiff = minInterval; - if (index > 0) { - currentDiff = Math.abs(xValues[index - 1] - currentXvalue); - } - const singleUnitInterval = getIntervalInMs( - currentXvalue, - esValue, - esUnit as unitOfTime.Base, - timeZone - ); - return Math.min(minInterval, singleUnitInterval, currentDiff); - }, Number.MAX_SAFE_INTEGER); -} - export class DiscoverHistogram extends Component { public static propTypes = { chartData: PropTypes.object, @@ -132,10 +80,10 @@ export class DiscoverHistogram extends Component + getServices().theme.chartsBaseTheme$, + ]).subscribe(([chartsTheme, chartsBaseTheme]) => this.setState({ chartsTheme, chartsBaseTheme }) ); } @@ -171,40 +119,6 @@ export class DiscoverHistogram extends Component ( - headerData: TooltipValue - ): JSX.Element | string => { - const headerDataValue = headerData.value; - const formattedValue = this.formatXValue(headerDataValue); - - const partialDataText = i18n.translate('discover.histogram.partialData.bucketTooltipText', { - defaultMessage: - 'The selected time range does not include this entire bucket, it may contain partial data.', - }); - - if (headerDataValue < domainStart || headerDataValue + xInterval > domainEnd) { - return ( - - - - - - {partialDataText} - - -

{formattedValue}

-
- ); - } - - return formattedValue; - }; - public render() { const uiSettings = getServices().uiSettings; const timeZone = getTimezone(uiSettings); @@ -216,8 +130,9 @@ export class DiscoverHistogram extends Component domainStart ? domainStart : data[0]?.x; - const domainMax = domainEnd - xInterval > lastXValue ? domainEnd - xInterval : lastXValue; + const domainMin = Math.min(data[0]?.x, domainStart); + const domainMax = Math.max(domainEnd - xInterval, lastXValue); const xDomain = { min: domainMin, max: domainMax, - minInterval: findMinInterval(xValues, intervalESValue, intervalESUnit, timeZone), - }; - - // Domain end of 'now' will be milliseconds behind current time, so we extend time by 1 minute and check if - // the annotation is within this range; if so, the line annotation uses the domainEnd as its value - const now = moment(); - const isAnnotationAtEdge = moment(domainEnd).add(60000).isAfter(now) && now.isAfter(domainEnd); - const lineAnnotationValue = isAnnotationAtEdge ? domainEnd : now; - - const lineAnnotationData = [ - { - dataValue: lineAnnotationValue, - }, - ]; - const isDarkMode = uiSettings.get('theme:darkMode'); - - const lineAnnotationStyle = { - line: { - strokeWidth: 2, - stroke: isDarkMode ? darkEuiTheme.euiColorDanger : lightEuiTheme.euiColorDanger, - opacity: 0.7, - }, + minInterval: getAdjustedInterval( + xValues, + intervalESValue, + intervalESUnit as unitOfTime.Base, + timeZone + ), }; - - const rectAnnotations = []; - if (domainStart !== domainMin) { - rectAnnotations.push({ - coordinates: { - x1: domainStart, - }, - }); - } - if (domainEnd !== domainMax) { - rectAnnotations.push({ - coordinates: { - x0: domainEnd, - }, - }); - } - - const rectAnnotationStyle = { - stroke: isDarkMode ? darkEuiTheme.euiColorLightShade : lightEuiTheme.euiColorDarkShade, - strokeWidth: 0, - opacity: isDarkMode ? 0.6 : 0.2, - fill: isDarkMode ? darkEuiTheme.euiColorLightShade : lightEuiTheme.euiColorDarkShade, - }; - const tooltipProps = { - headerFormatter: this.renderBarTooltip(xInterval, domainStart, domainEnd), + headerFormatter: renderEndzoneTooltip(xInterval, domainStart, domainEnd, this.formatXValue), type: TooltipType.VerticalCursor, }; @@ -313,19 +188,14 @@ export class DiscoverHistogram extends Component - - + 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 0000000000000..9ad19653a6c12 --- /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 634e3cfec3a0b..c1e4da0bab54d 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 0000000000000..1dbd31897d307 --- /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 6721f7a03584c..1244a0e229cdb 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 b143afd1988e6..d0c3907d31242 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 b2aa3a05d7eb0..4dec1f75ba322 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 8965446cc85fa..d893724f616d2 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 5c95214ef591b..efaff42c19e2f 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 0fc7c7965010b..d537ef2bd0c5c 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 c7c71656bceb2..c0e13a84066ca 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 0361939fd07e6..cb78fac5471a9 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 eb83641448986..b784a46127305 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 dea4a88bda082..ce6a1cc20fc4d 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 cbaeddf472d52..be034d125dcee 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 0b34bea810520..92900059668db 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 db71b94ac855f..23696612fd82a 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 fd6ea3b9aa2b2..99d21198dd151 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 c3b1496b8eca8..d9fb063a5bb56 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 df24d9c0393fe..c41ecaabe8479 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 5118a1a8818c0..a417fb3938b8a 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 03818fccda0bc..a401795c498b3 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 24785dd50a032..531fbcee94db6 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 2c298b437a118..74bb70e913bcc 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 72e2f51c37e4c..19af93b67aca0 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 88aca4c07ee31..fca1694747ce2 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 bb1f5dd9270d5..404df2db019a1 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 e52d4d153882f..4ebd626e70fc3 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 894325c8b65f7..eac2371ec66d0 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 7c1ab11f75027..8b8678371dd83 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/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts index 60d05890028d1..5cb837f816ff2 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts @@ -34,7 +34,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[eCommerce] Sales by Category', }), visState: - '{"title":"[eCommerce] Sales by Category","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Sum of total_quantity"}}],"seriesParams":[{"show":"true","type":"area","mode":"stacked","data":{"label":"Sum of total_quantity","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"linear","valueAxis":"ValueAxis-1"}],"addTooltip":true,"addLegend":true,"legendPosition":"top","times":[],"addTimeMarker":false},"aggs":[{"id":"1","enabled":true,"type":"sum","schema":"metric","params":{"field":"total_quantity"}},{"id":"2","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"order_date","interval":"auto","time_zone":"America/New_York","drop_partials":false,"customInterval":"2h","min_doc_count":1,"extended_bounds":{}}},{"id":"3","enabled":true,"type":"terms","schema":"group","params":{"field":"category.keyword","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}', + '{"title":"[eCommerce] Sales by Category","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Sum of total_quantity"}}],"seriesParams":[{"show":"true","type":"area","mode":"stacked","data":{"label":"Sum of total_quantity","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"linear","valueAxis":"ValueAxis-1"}],"addTooltip":true,"addLegend":true,"legendPosition":"top","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"sum","schema":"metric","params":{"field":"total_quantity"}},{"id":"2","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"order_date","interval":"auto","drop_partials":false,"min_doc_count":1,"extended_bounds":{}}},{"id":"3","enabled":true,"type":"terms","schema":"group","params":{"field":"category.keyword","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}', uiStateJSON: '{}', description: '', version: 1, diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts index e65b6ad40651b..6b06a526a1239 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts @@ -56,7 +56,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Flights] Flight Count and Average Ticket Price', }), visState: - '{"title":"[Flights] Flight Count and Average Ticket Price","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Average Ticket Price"}},{"id":"ValueAxis-2","name":"RightAxis-1","type":"value","position":"right","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Flight Count"}}],"seriesParams":[{"show":true,"mode":"stacked","type":"area","drawLinesBetweenPoints":true,"showCircles":false,"interpolate":"linear","lineWidth":2,"data":{"id":"5","label":"Flight Count"},"valueAxis":"ValueAxis-2"},{"show":true,"mode":"stacked","type":"line","drawLinesBetweenPoints":false,"showCircles":true,"interpolate":"linear","data":{"id":"4","label":"Average Ticket Price"},"valueAxis":"ValueAxis-1","lineWidth":2}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"radiusRatio":13},"aggs":[{"id":"3","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","customInterval":"2h","min_doc_count":1,"extended_bounds":{}}},{"id":"5","enabled":true,"type":"count","schema":"metric","params":{"customLabel":"Flight Count"}},{"id":"4","enabled":true,"type":"avg","schema":"metric","params":{"field":"AvgTicketPrice","customLabel":"Average Ticket Price"}},{"id":"2","enabled":true,"type":"avg","schema":"radius","params":{"field":"AvgTicketPrice"}}]}', + '{"title":"[Flights] Flight Count and Average Ticket Price","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Average Ticket Price"}},{"id":"ValueAxis-2","name":"RightAxis-1","type":"value","position":"right","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Flight Count"}}],"seriesParams":[{"show":true,"mode":"stacked","type":"area","drawLinesBetweenPoints":true,"showCircles":false,"interpolate":"linear","lineWidth":2,"data":{"id":"5","label":"Flight Count"},"valueAxis":"ValueAxis-2"},{"show":true,"mode":"stacked","type":"line","drawLinesBetweenPoints":false,"showCircles":true,"interpolate":"linear","data":{"id":"4","label":"Average Ticket Price"},"valueAxis":"ValueAxis-1","lineWidth":2}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"radiusRatio":13,"detailedTooltip":true},"aggs":[{"id":"3","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","min_doc_count":1,"extended_bounds":{}}},{"id":"5","enabled":true,"type":"count","schema":"metric","params":{"customLabel":"Flight Count"}},{"id":"4","enabled":true,"type":"avg","schema":"metric","params":{"field":"AvgTicketPrice","customLabel":"Average Ticket Price"}},{"id":"2","enabled":true,"type":"avg","schema":"radius","params":{"field":"AvgTicketPrice"}}]}', uiStateJSON: '{"vis":{"legendOpen":true,"colors":{"Average Ticket Price":"#629E51","Flight Count":"#AEA2E0"}}}', description: '', @@ -133,7 +133,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Flights] Delay Type', }), visState: - '{"title":"[Flights] Delay Type","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"cardinal","valueAxis":"ValueAxis-1"}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","customInterval":"2h","min_doc_count":1,"extended_bounds":{}}},{"id":"3","enabled":true,"type":"terms","schema":"group","params":{"field":"FlightDelayType","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}', + '{"title":"[Flights] Delay Type","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"cardinal","valueAxis":"ValueAxis-1"}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","min_doc_count":1,"extended_bounds":{}}},{"id":"3","enabled":true,"type":"terms","schema":"group","params":{"field":"FlightDelayType","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing"}}]}', uiStateJSON: '{}', description: '', version: 1, @@ -176,7 +176,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Flights] Delay Buckets', }), visState: - '{"title":"[Flights] Delay Buckets","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"histogram","schema":"segment","params":{"field":"FlightDelayMin","interval":30,"extended_bounds":{},"customLabel":"Flight Delay Minutes"}}]}', + '{"title":"[Flights] Delay Buckets","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},{"id":"2","enabled":true,"type":"histogram","schema":"segment","params":{"field":"FlightDelayMin","interval":30,"extended_bounds":{},"customLabel":"Flight Delay Minutes"}}]}', uiStateJSON: '{"vis":{"legendOpen":false}}', description: '', version: 1, @@ -198,7 +198,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Flights] Flight Delays', }), visState: - '{"title":"[Flights] Flight Delays","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"left","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"BottomAxis-1","type":"value","position":"bottom","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{"customLabel":""}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"FlightDelay","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing","customLabel":"Flight Delays"}}]}', + '{"title":"[Flights] Flight Delays","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"left","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"BottomAxis-1","type":"value","position":"bottom","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{"customLabel":""}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"FlightDelay","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing","customLabel":"Flight Delays"}}]}', uiStateJSON: '{}', description: '', version: 1, @@ -220,7 +220,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Flights] Flight Cancellations', }), visState: - '{"title":"[Flights] Flight Cancellations","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"left","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"BottomAxis-1","type":"value","position":"bottom","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{"customLabel":""}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"Cancelled","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing","customLabel":"Flight Cancellations"}}]}', + '{"title":"[Flights] Flight Cancellations","type":"histogram","params":{"type":"histogram","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"left","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"BottomAxis-1","type":"value","position":"bottom","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Count"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Count","id":"1"},"valueAxis":"ValueAxis-1","drawLinesBetweenPoints":true,"showCircles":true}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"count","schema":"metric","params":{"customLabel":""}},{"id":"2","enabled":true,"type":"terms","schema":"segment","params":{"field":"Cancelled","size":5,"order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","missingBucket":false,"missingBucketLabel":"Missing","customLabel":"Flight Cancellations"}}]}', uiStateJSON: '{}', description: '', version: 1, diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts index 068ba66c4b0de..7b891107cdfb0 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -33,7 +33,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: '[Logs] Unique Visitors vs. Average Bytes', }), visState: - '{"title":"[Logs] Unique Visitors vs. Average Bytes","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Avg. Bytes"}},{"id":"ValueAxis-2","name":"RightAxis-1","type":"value","position":"right","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Unique Visitors"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Avg. Bytes","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"linear","valueAxis":"ValueAxis-1"},{"show":true,"mode":"stacked","type":"line","drawLinesBetweenPoints":false,"showCircles":true,"interpolate":"linear","data":{"id":"2","label":"Unique Visitors"},"valueAxis":"ValueAxis-2"}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"radiusRatio":17},"aggs":[{"id":"1","enabled":true,"type":"avg","schema":"metric","params":{"field":"bytes","customLabel":"Avg. Bytes"}},{"id":"2","enabled":true,"type":"cardinality","schema":"metric","params":{"field":"clientip","customLabel":"Unique Visitors"}},{"id":"3","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","time_zone":"America/Los_Angeles","customInterval":"2h","min_doc_count":1,"extended_bounds":{}}},{"id":"4","enabled":true,"type":"count","schema":"radius","params":{}}]}', + '{"title":"[Logs] Unique Visitors vs. Average Bytes","type":"area","params":{"type":"area","grid":{"categoryLines":false,"style":{"color":"#eee"}},"categoryAxes":[{"id":"CategoryAxis-1","type":"category","position":"bottom","show":true,"style":{},"scale":{"type":"linear"},"labels":{"show":true,"truncate":100,"filter":true},"title":{}}],"valueAxes":[{"id":"ValueAxis-1","name":"LeftAxis-1","type":"value","position":"left","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Avg. Bytes"}},{"id":"ValueAxis-2","name":"RightAxis-1","type":"value","position":"right","show":true,"style":{},"scale":{"type":"linear","mode":"normal"},"labels":{"show":true,"rotate":0,"filter":false,"truncate":100},"title":{"text":"Unique Visitors"}}],"seriesParams":[{"show":"true","type":"histogram","mode":"stacked","data":{"label":"Avg. Bytes","id":"1"},"drawLinesBetweenPoints":true,"showCircles":true,"interpolate":"linear","valueAxis":"ValueAxis-1"},{"show":true,"mode":"stacked","type":"line","drawLinesBetweenPoints":false,"showCircles":true,"interpolate":"linear","data":{"id":"2","label":"Unique Visitors"},"valueAxis":"ValueAxis-2"}],"addTooltip":true,"addLegend":true,"legendPosition":"right","times":[],"addTimeMarker":false,"radiusRatio":17,"detailedTooltip":true},"aggs":[{"id":"1","enabled":true,"type":"avg","schema":"metric","params":{"field":"bytes","customLabel":"Avg. Bytes"}},{"id":"2","enabled":true,"type":"cardinality","schema":"metric","params":{"field":"clientip","customLabel":"Unique Visitors"}},{"id":"3","enabled":true,"type":"date_histogram","schema":"segment","params":{"field":"timestamp","interval":"auto","min_doc_count":1,"extended_bounds":{}}},{"id":"4","enabled":true,"type":"count","schema":"radius","params":{}}]}', uiStateJSON: '{"vis":{"colors":{"Avg. Bytes":"#70DBED","Unique Visitors":"#0A437C"}}}', description: '', version: 1, 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 a7681e1766427..64f1088dc3392 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 e88d90fe5b24b..65cc3643a88cb 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 83cac1d456a3a..dee9ca4d32c5f 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 6c2e0a2c926ad..5dd39d172e1c2 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/kibana_utils/server/index.ts b/src/plugins/kibana_utils/server/index.ts index d994a4940bdfd..a0e933f997dd5 100644 --- a/src/plugins/kibana_utils/server/index.ts +++ b/src/plugins/kibana_utils/server/index.ts @@ -28,3 +28,5 @@ export { Set, url, } from '../common'; + +export { KbnServerError, reportServerError } from './report_server_error'; diff --git a/src/plugins/kibana_utils/server/report_server_error.ts b/src/plugins/kibana_utils/server/report_server_error.ts new file mode 100644 index 0000000000000..43b4dba3d039b --- /dev/null +++ b/src/plugins/kibana_utils/server/report_server_error.ts @@ -0,0 +1,39 @@ +/* + * 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 { KibanaResponseFactory } from 'kibana/server'; +import { KbnError } from '../common'; + +export class KbnServerError extends KbnError { + constructor(message: string, public readonly statusCode: number) { + super(message); + } +} + +export function reportServerError(res: KibanaResponseFactory, err: any) { + return res.customError({ + statusCode: err.statusCode ?? 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); +} diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index bf03c649fa6b4..81026927380e0 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -58,11 +58,12 @@ export class ManagementPlugin implements Plugin { 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 090893964c881..46ae17171203c 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 e63e46af6e8ca..e1feea4b6cbe1 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 fc44a4db7cf5e..6ebbfcfb91336 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 d223c0abcccb7..7890e4bab44a3 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 fdb75e9a426e9..84a7ae45fc7b8 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 2384dfab13c8c..808cb1f3fbca0 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 ecbf4d1f7b988..6bba87e85eb95 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 87bca03f8c3ba..0fc261b3e1fb3 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/trigger.ts b/src/plugins/ui_actions/public/triggers/trigger.ts index 2c019b09881d1..1b1231c132dde 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 04a75cb3a53d0..7e7fba0ba80d3 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 0266a755be926..62fac245514cd 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_default_editor/public/components/agg_add.tsx b/src/plugins/vis_default_editor/public/components/agg_add.tsx index e78f2fcc4453c..2da7b33139a8e 100644 --- a/src/plugins/vis_default_editor/public/components/agg_add.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_add.tsx @@ -76,10 +76,17 @@ function DefaultEditorAggAdd({ ); - const isSchemaDisabled = (schema: Schema): boolean => { + const isMaxedCount = (schema: Schema): boolean => { const count = group.filter((agg) => agg.schema === schema.name).length; return count >= schema.max; }; + const isSchemaDisabled = (schema: Schema, maxedCount: boolean): boolean => { + return schema.disabled ?? maxedCount; + }; + const maxTooltipText = i18n.translate('visDefaultEditor.aggAdd.maxBuckets', { + defaultMessage: 'Max {groupNameLabel} count reached', + values: { groupNameLabel }, + }); return ( @@ -109,16 +116,21 @@ function DefaultEditorAggAdd({ )} ( - onSelectSchema(schema)} - > - {schema.title} - - ))} + items={schemas.map((schema) => { + const maxedCount = isMaxedCount(schema); + + return ( + onSelectSchema(schema)} + toolTipContent={schema.tooltip ?? (maxedCount ? maxTooltipText : undefined)} + > + {schema.title} + + ); + })} /> diff --git a/src/plugins/vis_default_editor/public/components/sidebar/use_option_tabs.ts b/src/plugins/vis_default_editor/public/components/sidebar/use_option_tabs.ts index 337533df50fad..90e2d792d3597 100644 --- a/src/plugins/vis_default_editor/public/components/sidebar/use_option_tabs.ts +++ b/src/plugins/vis_default_editor/public/components/sidebar/use_option_tabs.ts @@ -20,9 +20,10 @@ import { useCallback, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { Vis } from 'src/plugins/visualizations/public'; -import { DefaultEditorDataTab, DefaultEditorDataTabProps } from './data_tab'; +import { Vis } from '../../../../visualizations/public'; + import { VisOptionsProps } from '../../vis_options_props'; +import { DefaultEditorDataTab, DefaultEditorDataTabProps } from './data_tab'; export interface OptionTab { editor: React.ComponentType; diff --git a/src/plugins/vis_default_editor/public/default_editor_controller.tsx b/src/plugins/vis_default_editor/public/default_editor_controller.tsx index 707b14c23ea75..f44ea3e203b05 100644 --- a/src/plugins/vis_default_editor/public/default_editor_controller.tsx +++ b/src/plugins/vis_default_editor/public/default_editor_controller.tsx @@ -25,6 +25,7 @@ import { EuiErrorBoundary, EuiLoadingChart } from '@elastic/eui'; import { EditorRenderProps, IEditorController } from 'src/plugins/visualize/public'; import { Vis, VisualizeEmbeddableContract } from 'src/plugins/visualizations/public'; +// @ts-ignore const DefaultEditor = lazy(() => import('./default_editor')); class DefaultEditorController implements IEditorController { diff --git a/src/plugins/vis_default_editor/public/schemas.ts b/src/plugins/vis_default_editor/public/schemas.ts index d95a6252331bf..7ecb4e54726b8 100644 --- a/src/plugins/vis_default_editor/public/schemas.ts +++ b/src/plugins/vis_default_editor/public/schemas.ts @@ -17,6 +17,7 @@ * under the License. */ +import { ReactNode } from 'react'; import _, { defaults } from 'lodash'; import { Optional } from '@kbn/utility-types'; @@ -42,6 +43,8 @@ export interface Schema { hideCustomLabel?: boolean; mustBeFirst?: boolean; aggSettings?: any; + disabled?: boolean; + tooltip?: ReactNode; } export class Schemas implements ISchemas { diff --git a/src/plugins/vis_type_markdown/public/to_ast.test.ts b/src/plugins/vis_type_markdown/public/to_ast.test.ts index 1ad1fa0ee2517..4c7b570aef05d 100644 --- a/src/plugins/vis_type_markdown/public/to_ast.test.ts +++ b/src/plugins/vis_type_markdown/public/to_ast.test.ts @@ -42,12 +42,14 @@ describe('markdown vis toExpressionAst function', () => { it('without params', () => { vis.params = {}; + // @ts-expect-error const actual = toExpressionAst(vis); expect(actual).toMatchSnapshot(); }); it('with params', () => { vis.params = { markdown: '### my markdown', fontSize: 15, openLinksInNewTab: true }; + // @ts-expect-error const actual = toExpressionAst(vis); expect(actual).toMatchSnapshot(); }); diff --git a/src/plugins/vis_type_markdown/public/to_ast.ts b/src/plugins/vis_type_markdown/public/to_ast.ts index 9b481218b42ea..dc61acf722ad4 100644 --- a/src/plugins/vis_type_markdown/public/to_ast.ts +++ b/src/plugins/vis_type_markdown/public/to_ast.ts @@ -17,11 +17,11 @@ * under the License. */ -import { Vis } from '../../visualizations/public'; +import { VisToExpressionAst } from '../../visualizations/public'; import { buildExpression, buildExpressionFunction } from '../../expressions/public'; import { MarkdownVisExpressionFunctionDefinition } from './markdown_fn'; -export const toExpressionAst = (vis: Vis) => { +export const toExpressionAst: VisToExpressionAst = (vis) => { const { markdown, fontSize, openLinksInNewTab } = vis.params; const markdownVis = buildExpressionFunction( diff --git a/src/plugins/vis_type_metric/public/components/metric_vis_options.tsx b/src/plugins/vis_type_metric/public/components/metric_vis_options.tsx index d0a7412238871..d87a0da740d75 100644 --- a/src/plugins/vis_type_metric/public/components/metric_vis_options.tsx +++ b/src/plugins/vis_type_metric/public/components/metric_vis_options.tsx @@ -31,7 +31,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { - ColorModes, + ColorMode, ColorRanges, ColorSchemaOptions, SwitchOption, @@ -86,7 +86,7 @@ function MetricVisOptions({ ); const setColorMode: EuiButtonGroupProps['onChange'] = useCallback( - (id: string) => setMetricValue('metricColorMode', id as ColorModes), + (id: string) => setMetricValue('metricColorMode', id as ColorMode), [setMetricValue] ); @@ -158,7 +158,7 @@ function MetricVisOptions({ colorSchemas={vis.type.editorConfig.collections.colorSchemas} disabled={ stateParams.metric.colorsRange.length === 1 || - stateParams.metric.metricColorMode === ColorModes.NONE + stateParams.metric.metricColorMode === ColorMode.None } invertColors={stateParams.metric.invertColors} setValue={setMetricValue as SetColorSchemaOptionsValue} diff --git a/src/plugins/vis_type_metric/public/metric_vis_fn.ts b/src/plugins/vis_type_metric/public/metric_vis_fn.ts index ae6dc6683852e..783ca3d3906ea 100644 --- a/src/plugins/vis_type_metric/public/metric_vis_fn.ts +++ b/src/plugins/vis_type_metric/public/metric_vis_fn.ts @@ -27,14 +27,14 @@ import { Style, } from '../../expressions/public'; import { visType, DimensionsVisParam, VisParams } from './types'; -import { ColorSchemas, vislibColorMaps, ColorModes } from '../../charts/public'; +import { ColorSchemas, vislibColorMaps, ColorMode } from '../../charts/public'; export type Input = Datatable; interface Arguments { percentageMode: boolean; colorSchema: ColorSchemas; - colorMode: ColorModes; + colorMode: ColorMode; useRanges: boolean; invertColors: boolean; showLabels: boolean; @@ -86,7 +86,7 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ colorMode: { types: ['string'], default: '"None"', - options: [ColorModes.NONE, ColorModes.LABELS, ColorModes.BACKGROUND], + options: [ColorMode.None, ColorMode.Labels, ColorMode.Background], help: i18n.translate('visTypeMetric.function.colorMode.help', { defaultMessage: 'Which part of metric to color', }), @@ -197,8 +197,8 @@ export const createMetricVisFn = (): MetricVisExpressionFunctionDefinition => ({ invertColors: args.invertColors, style: { bgFill: args.bgFill, - bgColor: args.colorMode === ColorModes.BACKGROUND, - labelColor: args.colorMode === ColorModes.LABELS, + bgColor: args.colorMode === ColorMode.Background, + labelColor: args.colorMode === ColorMode.Labels, subText: args.subText, fontSize, }, diff --git a/src/plugins/vis_type_metric/public/metric_vis_type.ts b/src/plugins/vis_type_metric/public/metric_vis_type.ts index f7c74e324053e..ba8f27b9412a2 100644 --- a/src/plugins/vis_type_metric/public/metric_vis_type.ts +++ b/src/plugins/vis_type_metric/public/metric_vis_type.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { BaseVisTypeOptions } from 'src/plugins/visualizations/public'; import { MetricVisOptions } from './components/metric_vis_options'; -import { ColorSchemas, colorSchemas, ColorModes } from '../../charts/public'; +import { ColorSchemas, colorSchemas, ColorMode } from '../../charts/public'; import { AggGroupNames } from '../../data/public'; import { Schemas } from '../../vis_default_editor/public'; import { toExpressionAst } from './to_ast'; @@ -42,7 +42,7 @@ export const createMetricVisTypeDefinition = (): BaseVisTypeOptions => ({ percentageMode: false, useRanges: false, colorSchema: ColorSchemas.GreenToRed, - metricColorMode: ColorModes.NONE, + metricColorMode: ColorMode.None, colorsRange: [{ from: 0, to: 10000 }], labels: { show: true, @@ -62,19 +62,19 @@ export const createMetricVisTypeDefinition = (): BaseVisTypeOptions => ({ collections: { metricColorMode: [ { - id: ColorModes.NONE, + id: ColorMode.None, label: i18n.translate('visTypeMetric.colorModes.noneOptionLabel', { defaultMessage: 'None', }), }, { - id: ColorModes.LABELS, + id: ColorMode.Labels, label: i18n.translate('visTypeMetric.colorModes.labelsOptionLabel', { defaultMessage: 'Labels', }), }, { - id: ColorModes.BACKGROUND, + id: ColorMode.Background, label: i18n.translate('visTypeMetric.colorModes.backgroundOptionLabel', { defaultMessage: 'Background', }), diff --git a/src/plugins/vis_type_metric/public/types.ts b/src/plugins/vis_type_metric/public/types.ts index e1f2c7721a426..e0ebfb36fa37d 100644 --- a/src/plugins/vis_type_metric/public/types.ts +++ b/src/plugins/vis_type_metric/public/types.ts @@ -19,7 +19,7 @@ import { Range } from '../../expressions/public'; import { SchemaConfig } from '../../visualizations/public'; -import { ColorModes, Labels, Style, ColorSchemas } from '../../charts/public'; +import { ColorMode, Labels, Style, ColorSchemas } from '../../charts/public'; export const visType = 'metric'; @@ -32,7 +32,7 @@ export interface MetricVisParam { percentageMode: boolean; useRanges: boolean; colorSchema: ColorSchemas; - metricColorMode: ColorModes; + metricColorMode: ColorMode; colorsRange: Range[]; labels: Labels; invertColors: boolean; 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 2d38acc57519f..a1e4fad719dc4 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 68bad16972ec2..cc43fc6bcb582 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/application/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap index 7ded8e2254aa9..fceb9c3fdb819 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/decorators/area_decorator.js should render and match a snapshot 1`] = ` - should render and match a snapshot 1`] = ` - , domNode diff --git a/src/plugins/vis_type_timeseries/server/routes/fields.ts b/src/plugins/vis_type_timeseries/server/routes/fields.ts index a9a890845d154..e787fd8d08a29 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/kibana.json b/src/plugins/vis_type_vislib/kibana.json index 720abff16b7c7..175c21f47c182 100644 --- a/src/plugins/vis_type_vislib/kibana.json +++ b/src/plugins/vis_type_vislib/kibana.json @@ -4,6 +4,5 @@ "server": true, "ui": true, "requiredPlugins": ["charts", "data", "expressions", "visualizations", "kibanaLegacy"], - "optionalPlugins": ["visTypeXy"], - "requiredBundles": ["kibanaUtils", "visDefaultEditor"] + "requiredBundles": ["kibanaUtils", "visDefaultEditor", "visTypeXy"] } diff --git a/src/plugins/vis_type_vislib/public/area.ts b/src/plugins/vis_type_vislib/public/area.ts index ec7bce254f586..389daca6d1776 100644 --- a/src/plugins/vis_type_vislib/public/area.ts +++ b/src/plugins/vis_type_vislib/public/area.ts @@ -17,172 +17,14 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { palettes } from '@elastic/eui/lib/services'; -// @ts-ignore -import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; +import { xyVisTypes } from '../../vis_type_xy/public'; +import { BaseVisTypeOptions } from '../../visualizations/public'; -import { AggGroupNames } from '../../data/public'; -import { Schemas } from '../../vis_default_editor/public'; -import { - Positions, - ChartTypes, - ChartModes, - InterpolationModes, - AxisTypes, - ScaleTypes, - AxisModes, - ThresholdLineStyles, - getConfigCollections, -} from './utils/collections'; -import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { Rotates } from '../../charts/public'; -import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; import { toExpressionAst } from './to_ast'; import { BasicVislibParams } from './types'; export const areaVisTypeDefinition: BaseVisTypeOptions = { - name: 'area', - title: i18n.translate('visTypeVislib.area.areaTitle', { defaultMessage: 'Area' }), - icon: 'visArea', - description: i18n.translate('visTypeVislib.area.areaDescription', { - defaultMessage: 'Emphasize the data between an axis and a line.', - }), - getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + ...(xyVisTypes.area() as BaseVisTypeOptions), toExpressionAst, - visConfig: { - defaults: { - type: 'area', - grid: { - categoryLines: false, - }, - categoryAxes: [ - { - id: 'CategoryAxis-1', - type: AxisTypes.CATEGORY, - position: Positions.BOTTOM, - show: true, - style: {}, - scale: { - type: ScaleTypes.LINEAR, - }, - labels: { - show: true, - filter: true, - truncate: 100, - }, - title: {}, - }, - ], - valueAxes: [ - { - id: 'ValueAxis-1', - name: 'LeftAxis-1', - type: AxisTypes.VALUE, - position: Positions.LEFT, - show: true, - style: {}, - scale: { - type: ScaleTypes.LINEAR, - mode: AxisModes.NORMAL, - }, - labels: { - show: true, - rotate: Rotates.HORIZONTAL, - filter: false, - truncate: 100, - }, - title: { - text: countLabel, - }, - }, - ], - seriesParams: [ - { - show: true, - type: ChartTypes.AREA, - mode: ChartModes.STACKED, - data: { - label: countLabel, - id: '1', - }, - drawLinesBetweenPoints: true, - lineWidth: 2, - showCircles: true, - interpolate: InterpolationModes.LINEAR, - valueAxis: 'ValueAxis-1', - }, - ], - addTooltip: true, - addLegend: true, - legendPosition: Positions.RIGHT, - times: [], - addTimeMarker: false, - thresholdLine: { - show: false, - value: 10, - width: 1, - style: ThresholdLineStyles.FULL, - color: euiPaletteColorBlind()[9], - }, - labels: {}, - }, - }, - editorConfig: { - collections: getConfigCollections(), - optionTabs: getAreaOptionTabs(), - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('visTypeVislib.area.metricsTitle', { - defaultMessage: 'Y-axis', - }), - aggFilter: ['!geo_centroid', '!geo_bounds'], - min: 1, - defaults: [{ schema: 'metric', type: 'count' }], - }, - { - group: AggGroupNames.Metrics, - name: 'radius', - title: i18n.translate('visTypeVislib.area.radiusTitle', { - defaultMessage: 'Dot size', - }), - min: 0, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'], - }, - { - group: AggGroupNames.Buckets, - name: 'segment', - title: i18n.translate('visTypeVislib.area.segmentTitle', { - defaultMessage: 'X-axis', - }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], - }, - { - group: AggGroupNames.Buckets, - name: 'group', - title: i18n.translate('visTypeVislib.area.groupTitle', { - defaultMessage: 'Split series', - }), - min: 0, - max: 3, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], - }, - { - group: AggGroupNames.Buckets, - name: 'split', - title: i18n.translate('visTypeVislib.area.splitTitle', { - defaultMessage: 'Split chart', - }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], - }, - ]), - }, + visualization: undefined, }; diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx b/src/plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx deleted file mode 100644 index 63881fea1ad88..0000000000000 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx +++ /dev/null @@ -1,248 +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 React from 'react'; -import { mount, shallow } from 'enzyme'; - -import { IAggConfig, IAggType } from 'src/plugins/data/public'; -import MetricsAxisOptions from './index'; -import { BasicVislibParams, SeriesParam, ValueAxis } from '../../../types'; -import { ValidationVisOptionsProps } from '../../common'; -import { Positions } from '../../../utils/collections'; -import { ValueAxesPanel } from './value_axes_panel'; -import { CategoryAxisPanel } from './category_axis_panel'; -import { ChartTypes } from '../../../utils/collections'; -import { defaultValueAxisId, valueAxis, seriesParam, categoryAxis } from './mocks'; - -jest.mock('./series_panel', () => ({ - SeriesPanel: () => 'SeriesPanel', -})); -jest.mock('./category_axis_panel', () => ({ - CategoryAxisPanel: () => 'CategoryAxisPanel', -})); -jest.mock('./value_axes_panel', () => ({ - ValueAxesPanel: () => 'ValueAxesPanel', -})); - -const SERIES_PARAMS = 'seriesParams'; -const VALUE_AXES = 'valueAxes'; - -const aggCount: IAggConfig = { - id: '1', - type: { name: 'count' }, - makeLabel: () => 'Count', -} as IAggConfig; - -const aggAverage: IAggConfig = { - id: '2', - type: { name: 'average' } as IAggType, - makeLabel: () => 'Average', -} as IAggConfig; - -const createAggs = (aggs: any[]) => ({ - aggs, - bySchemaName: () => aggs, -}); - -describe('MetricsAxisOptions component', () => { - let setValue: jest.Mock; - let defaultProps: ValidationVisOptionsProps; - let axis: ValueAxis; - let axisRight: ValueAxis; - let chart: SeriesParam; - - beforeEach(() => { - setValue = jest.fn(); - - axis = { - ...valueAxis, - name: 'LeftAxis-1', - position: Positions.LEFT, - }; - axisRight = { - ...valueAxis, - id: 'ValueAxis-2', - name: 'RightAxis-1', - position: Positions.RIGHT, - }; - chart = { - ...seriesParam, - type: ChartTypes.AREA, - }; - - defaultProps = { - aggs: createAggs([aggCount]), - isTabSelected: true, - vis: { - type: { - type: ChartTypes.AREA, - schemas: { metrics: [{ name: 'metric' }] }, - }, - setState: jest.fn(), - serialize: jest.fn(), - }, - stateParams: { - valueAxes: [axis], - seriesParams: [chart], - categoryAxes: [categoryAxis], - grid: { valueAxis: defaultValueAxisId }, - }, - setValue, - } as any; - }); - - it('should init with the default set of props', () => { - const comp = shallow(); - - expect(comp).toMatchSnapshot(); - }); - - describe('useEffect', () => { - it('should update series when new agg is added', () => { - const comp = mount(); - comp.setProps({ - aggs: createAggs([aggCount, aggAverage]), - }); - - const updatedSeries = [chart, { ...chart, data: { id: '2', label: aggAverage.makeLabel() } }]; - expect(setValue).toHaveBeenLastCalledWith(SERIES_PARAMS, updatedSeries); - }); - - it('should update series when new agg label is changed', () => { - const comp = mount(); - const agg = { id: aggCount.id, makeLabel: () => 'New label' }; - comp.setProps({ - aggs: createAggs([agg]), - }); - - const updatedSeries = [{ ...chart, data: { id: agg.id, label: agg.makeLabel() } }]; - expect(setValue).toHaveBeenCalledWith(SERIES_PARAMS, updatedSeries); - }); - }); - - describe('updateAxisTitle', () => { - it('should not update the value axis title if custom title was set', () => { - defaultProps.stateParams.valueAxes[0].title.text = 'Custom title'; - const comp = mount(); - const newAgg = { - ...aggCount, - makeLabel: () => 'Custom label', - }; - comp.setProps({ - aggs: createAggs([newAgg]), - }); - const updatedValues = [{ ...axis, title: { text: newAgg.makeLabel() } }]; - expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES, updatedValues); - }); - - it('should set the custom title to match the value axis label when only one agg exists for that axis', () => { - const comp = mount(); - const agg = { - id: aggCount.id, - params: { customLabel: 'Custom label' }, - makeLabel: () => 'Custom label', - }; - comp.setProps({ - aggs: createAggs([agg]), - }); - - const updatedSeriesParams = [{ ...chart, data: { ...chart.data, label: agg.makeLabel() } }]; - const updatedValues = [{ ...axis, title: { text: agg.makeLabel() } }]; - - expect(setValue).toHaveBeenCalledTimes(5); - expect(setValue).toHaveBeenNthCalledWith(3, SERIES_PARAMS, updatedSeriesParams); - expect(setValue).toHaveBeenNthCalledWith(5, SERIES_PARAMS, updatedSeriesParams); - expect(setValue).toHaveBeenNthCalledWith(4, VALUE_AXES, updatedValues); - }); - - it('should not set the custom title to match the value axis label when more than one agg exists for that axis', () => { - const comp = mount(); - const agg = { id: aggCount.id, makeLabel: () => 'Custom label' }; - comp.setProps({ - aggs: createAggs([agg, aggAverage]), - stateParams: { - ...defaultProps.stateParams, - seriesParams: [chart, chart], - }, - }); - - expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES); - }); - - it('should not overwrite the custom title with the value axis label if the custom title has been changed', () => { - defaultProps.stateParams.valueAxes[0].title.text = 'Custom title'; - const comp = mount(); - const agg = { - id: aggCount.id, - params: { customLabel: 'Custom label' }, - makeLabel: () => 'Custom label', - }; - comp.setProps({ - aggs: createAggs([agg]), - }); - - expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES); - }); - }); - - it('should add value axis', () => { - const comp = shallow(); - comp.find(ValueAxesPanel).prop('addValueAxis')(); - - expect(setValue).toHaveBeenCalledWith(VALUE_AXES, [axis, axisRight]); - }); - - describe('removeValueAxis', () => { - beforeEach(() => { - defaultProps.stateParams.valueAxes = [axis, axisRight]; - }); - - it('should remove value axis', () => { - const comp = shallow(); - comp.find(ValueAxesPanel).prop('removeValueAxis')(axis); - - expect(setValue).toHaveBeenCalledWith(VALUE_AXES, [axisRight]); - }); - - it('should update seriesParams "valueAxis" prop', () => { - const updatedSeriesParam = { ...chart, valueAxis: 'ValueAxis-2' }; - const comp = shallow(); - comp.find(ValueAxesPanel).prop('removeValueAxis')(axis); - - expect(setValue).toHaveBeenCalledWith(SERIES_PARAMS, [updatedSeriesParam]); - }); - - it('should reset grid "valueAxis" prop', () => { - const updatedGrid = { valueAxis: undefined }; - defaultProps.stateParams.seriesParams[0].valueAxis = 'ValueAxis-2'; - const comp = shallow(); - comp.find(ValueAxesPanel).prop('removeValueAxis')(axis); - - expect(setValue).toHaveBeenCalledWith('grid', updatedGrid); - }); - }); - - it('should update axis value when when category position chnaged', () => { - const comp = shallow(); - comp.find(CategoryAxisPanel).prop('onPositionChanged')(Positions.LEFT); - - const updatedValues = [{ ...axis, name: 'BottomAxis-1', position: Positions.BOTTOM }]; - expect(setValue).toHaveBeenCalledWith(VALUE_AXES, updatedValues); - }); -}); diff --git a/src/plugins/vis_type_vislib/public/editor/collections.ts b/src/plugins/vis_type_vislib/public/editor/collections.ts new file mode 100644 index 0000000000000..f1caa0754b0b3 --- /dev/null +++ b/src/plugins/vis_type_vislib/public/editor/collections.ts @@ -0,0 +1,73 @@ +/* + * 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 { colorSchemas } from '../../../charts/public'; +import { getPositions, getScaleTypes } from '../../../vis_type_xy/public'; + +import { Alignment, GaugeType } from '../types'; + +export const getGaugeTypes = () => [ + { + text: i18n.translate('visTypeVislib.gauge.gaugeTypes.arcText', { + defaultMessage: 'Arc', + }), + value: GaugeType.Arc, + }, + { + text: i18n.translate('visTypeVislib.gauge.gaugeTypes.circleText', { + defaultMessage: 'Circle', + }), + value: GaugeType.Circle, + }, +]; + +export const getAlignments = () => [ + { + text: i18n.translate('visTypeVislib.gauge.alignmentAutomaticTitle', { + defaultMessage: 'Automatic', + }), + value: Alignment.Automatic, + }, + { + text: i18n.translate('visTypeVislib.gauge.alignmentHorizontalTitle', { + defaultMessage: 'Horizontal', + }), + value: Alignment.Horizontal, + }, + { + text: i18n.translate('visTypeVislib.gauge.alignmentVerticalTitle', { + defaultMessage: 'Vertical', + }), + value: Alignment.Vertical, + }, +]; + +export const getGaugeCollections = () => ({ + gaugeTypes: getGaugeTypes(), + alignments: getAlignments(), + colorSchemas, +}); + +export const getHeatmapCollections = () => ({ + legendPositions: getPositions(), + scales: getScaleTypes(), + colorSchemas, +}); diff --git a/src/plugins/vis_type_vislib/public/components/options/gauge/index.tsx b/src/plugins/vis_type_vislib/public/editor/components/gauge/index.tsx similarity index 100% rename from src/plugins/vis_type_vislib/public/components/options/gauge/index.tsx rename to src/plugins/vis_type_vislib/public/editor/components/gauge/index.tsx diff --git a/src/plugins/vis_type_vislib/public/components/options/gauge/labels_panel.tsx b/src/plugins/vis_type_vislib/public/editor/components/gauge/labels_panel.tsx similarity index 100% rename from src/plugins/vis_type_vislib/public/components/options/gauge/labels_panel.tsx rename to src/plugins/vis_type_vislib/public/editor/components/gauge/labels_panel.tsx diff --git a/src/plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx b/src/plugins/vis_type_vislib/public/editor/components/gauge/ranges_panel.tsx similarity index 100% rename from src/plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx rename to src/plugins/vis_type_vislib/public/editor/components/gauge/ranges_panel.tsx diff --git a/src/plugins/vis_type_vislib/public/components/options/gauge/style_panel.tsx b/src/plugins/vis_type_vislib/public/editor/components/gauge/style_panel.tsx similarity index 100% rename from src/plugins/vis_type_vislib/public/components/options/gauge/style_panel.tsx rename to src/plugins/vis_type_vislib/public/editor/components/gauge/style_panel.tsx diff --git a/src/plugins/vis_type_vislib/public/components/options/heatmap/index.tsx b/src/plugins/vis_type_vislib/public/editor/components/heatmap/index.tsx similarity index 97% rename from src/plugins/vis_type_vislib/public/components/options/heatmap/index.tsx rename to src/plugins/vis_type_vislib/public/editor/components/heatmap/index.tsx index 312cf60fda6b0..f5b853accb08e 100644 --- a/src/plugins/vis_type_vislib/public/components/options/heatmap/index.tsx +++ b/src/plugins/vis_type_vislib/public/editor/components/heatmap/index.tsx @@ -23,7 +23,8 @@ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; +import { VisOptionsProps } from '../../../../../vis_default_editor/public'; +import { ValueAxis } from '../../../../../vis_type_xy/public'; import { BasicOptions, ColorRanges, @@ -34,8 +35,8 @@ import { SetColorSchemaOptionsValue, SetColorRangeValue, } from '../../../../../charts/public'; + import { HeatmapVisParams } from '../../../heatmap'; -import { ValueAxis } from '../../../types'; import { LabelsPanel } from './labels_panel'; function HeatmapOptions(props: VisOptionsProps) { diff --git a/src/plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx b/src/plugins/vis_type_vislib/public/editor/components/heatmap/labels_panel.tsx similarity index 96% rename from src/plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx rename to src/plugins/vis_type_vislib/public/editor/components/heatmap/labels_panel.tsx index 4998a8fd02521..8ec06ea50ec12 100644 --- a/src/plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx +++ b/src/plugins/vis_type_vislib/public/editor/components/heatmap/labels_panel.tsx @@ -23,10 +23,11 @@ import { EuiColorPicker, EuiFormRow, EuiPanel, EuiSpacer, EuiTitle } from '@elas import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; -import { ValueAxis } from '../../../types'; -import { HeatmapVisParams } from '../../../heatmap'; +import { VisOptionsProps } from '../../../../../vis_default_editor/public'; import { SwitchOption } from '../../../../../charts/public'; +import { ValueAxis } from '../../../../../vis_type_xy/public'; + +import { HeatmapVisParams } from '../../../heatmap'; const VERTICAL_ROTATION = 270; diff --git a/src/plugins/vis_type_vislib/public/components/options/index.tsx b/src/plugins/vis_type_vislib/public/editor/components/index.tsx similarity index 71% rename from src/plugins/vis_type_vislib/public/components/options/index.tsx rename to src/plugins/vis_type_vislib/public/editor/components/index.tsx index 18c41bf289b11..ed8c8239a07b6 100644 --- a/src/plugins/vis_type_vislib/public/components/options/index.tsx +++ b/src/plugins/vis_type_vislib/public/editor/components/index.tsx @@ -19,18 +19,15 @@ import React, { lazy } from 'react'; -import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; -import { ValidationVisOptionsProps } from '../common'; +import { VisOptionsProps } from '../../../../vis_default_editor/public'; + import { GaugeVisParams } from '../../gauge'; import { PieVisParams } from '../../pie'; -import { BasicVislibParams } from '../../types'; import { HeatmapVisParams } from '../../heatmap'; const GaugeOptionsLazy = lazy(() => import('./gauge')); const PieOptionsLazy = lazy(() => import('./pie')); -const PointSeriesOptionsLazy = lazy(() => import('./point_series')); const HeatmapOptionsLazy = lazy(() => import('./heatmap')); -const MetricsAxisOptionsLazy = lazy(() => import('./metrics_axes')); export const GaugeOptions = (props: VisOptionsProps) => ( @@ -38,14 +35,6 @@ export const GaugeOptions = (props: VisOptionsProps) => ( export const PieOptions = (props: VisOptionsProps) => ; -export const PointSeriesOptions = (props: ValidationVisOptionsProps) => ( - -); - export const HeatmapOptions = (props: VisOptionsProps) => ( ); - -export const MetricsAxisOptions = (props: ValidationVisOptionsProps) => ( - -); diff --git a/src/plugins/vis_type_vislib/public/components/options/pie.tsx b/src/plugins/vis_type_vislib/public/editor/components/pie.tsx similarity index 96% rename from src/plugins/vis_type_vislib/public/components/options/pie.tsx rename to src/plugins/vis_type_vislib/public/editor/components/pie.tsx index 30828bfc6a3ea..1c3aa501b4d00 100644 --- a/src/plugins/vis_type_vislib/public/components/options/pie.tsx +++ b/src/plugins/vis_type_vislib/public/editor/components/pie.tsx @@ -22,9 +22,10 @@ import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; -import { TruncateLabelsOption } from '../common'; +import { VisOptionsProps } from '../../../../vis_default_editor/public'; import { BasicOptions, SwitchOption } from '../../../../charts/public'; +import { TruncateLabelsOption } from '../../../../vis_type_xy/public'; + import { PieVisParams } from '../../pie'; function PieOptions(props: VisOptionsProps) { diff --git a/src/plugins/vis_type_vislib/public/editor/index.ts b/src/plugins/vis_type_vislib/public/editor/index.ts new file mode 100644 index 0000000000000..2a73b1ad8fa68 --- /dev/null +++ b/src/plugins/vis_type_vislib/public/editor/index.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +export * from './collections'; +export * from './components'; diff --git a/src/plugins/vis_type_vislib/public/gauge.ts b/src/plugins/vis_type_vislib/public/gauge.ts index 7cc8e03c9e4c2..de32ee17a21bf 100644 --- a/src/plugins/vis_type_vislib/public/gauge.ts +++ b/src/plugins/vis_type_vislib/public/gauge.ts @@ -19,24 +19,25 @@ import { i18n } from '@kbn/i18n'; +import { ColorMode, ColorSchemas, ColorSchemaParams, Labels, Style } from '../../charts/public'; import { RangeValues, Schemas } from '../../vis_default_editor/public'; import { AggGroupNames } from '../../data/public'; -import { GaugeOptions } from './components/options'; -import { getGaugeCollections, Alignments, GaugeTypes } from './utils/collections'; -import { ColorModes, ColorSchemas, ColorSchemaParams, Labels, Style } from '../../charts/public'; -import { toExpressionAst } from './to_ast'; import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; -import { BasicVislibParams } from './types'; + +import { Alignment, GaugeType, BasicVislibParams, VislibChartType } from './types'; +import { getGaugeCollections } from './editor'; +import { toExpressionAst } from './to_ast'; +import { GaugeOptions } from './editor/components'; export interface Gauge extends ColorSchemaParams { backStyle: 'Full'; gaugeStyle: 'Full'; orientation: 'vertical'; type: 'meter'; - alignment: Alignments; + alignment: Alignment; colorsRange: RangeValues[]; extendRange: boolean; - gaugeType: GaugeTypes; + gaugeType: GaugeType; labels: Labels; percentageMode: boolean; outline?: boolean; @@ -67,20 +68,20 @@ export const gaugeVisTypeDefinition: BaseVisTypeOptions = { toExpressionAst, visConfig: { defaults: { - type: 'gauge', + type: VislibChartType.Gauge, addTooltip: true, addLegend: true, isDisplayWarning: false, gauge: { - alignment: Alignments.AUTOMATIC, + alignment: Alignment.Automatic, extendRange: true, percentageMode: false, - gaugeType: GaugeTypes.ARC, + gaugeType: GaugeType.Arc, gaugeStyle: 'Full', backStyle: 'Full', orientation: 'vertical', colorSchema: ColorSchemas.GreenToRed, - gaugeColorMode: ColorModes.LABELS, + gaugeColorMode: ColorMode.Labels, colorsRange: [ { from: 0, to: 50 }, { from: 50, to: 75 }, diff --git a/src/plugins/vis_type_vislib/public/goal.ts b/src/plugins/vis_type_vislib/public/goal.ts index 46878ca82e45a..56361421261fc 100644 --- a/src/plugins/vis_type_vislib/public/goal.ts +++ b/src/plugins/vis_type_vislib/public/goal.ts @@ -19,14 +19,14 @@ import { i18n } from '@kbn/i18n'; -import { GaugeOptions } from './components/options'; -import { getGaugeCollections, GaugeTypes } from './utils/collections'; -import { ColorModes, ColorSchemas } from '../../charts/public'; import { AggGroupNames } from '../../data/public'; import { Schemas } from '../../vis_default_editor/public'; -import { toExpressionAst } from './to_ast'; +import { ColorMode, ColorSchemas } from '../../charts/public'; import { BaseVisTypeOptions } from '../../visualizations/public'; -import { BasicVislibParams } from './types'; + +import { getGaugeCollections, GaugeOptions } from './editor'; +import { toExpressionAst } from './to_ast'; +import { GaugeType, BasicVislibParams } from './types'; export const goalVisTypeDefinition: BaseVisTypeOptions = { name: 'goal', @@ -46,13 +46,13 @@ export const goalVisTypeDefinition: BaseVisTypeOptions = { verticalSplit: false, autoExtend: false, percentageMode: true, - gaugeType: GaugeTypes.ARC, + gaugeType: GaugeType.Arc, gaugeStyle: 'Full', backStyle: 'Full', orientation: 'vertical', useRanges: false, colorSchema: ColorSchemas.GreenToRed, - gaugeColorMode: ColorModes.NONE, + gaugeColorMode: ColorMode.None, colorsRange: [{ from: 0, to: 10000 }], invertColors: false, labels: { diff --git a/src/plugins/vis_type_vislib/public/heatmap.ts b/src/plugins/vis_type_vislib/public/heatmap.ts index c408ac140dd46..4a815fd8b2c73 100644 --- a/src/plugins/vis_type_vislib/public/heatmap.ts +++ b/src/plugins/vis_type_vislib/public/heatmap.ts @@ -18,15 +18,17 @@ */ import { i18n } from '@kbn/i18n'; +import { Position } from '@elastic/charts'; import { RangeValues, Schemas } from '../../vis_default_editor/public'; import { AggGroupNames } from '../../data/public'; -import { AxisTypes, getHeatmapCollections, Positions, ScaleTypes } from './utils/collections'; -import { HeatmapOptions } from './components/options'; -import { TimeMarker } from './vislib/visualizations/time_marker'; -import { BasicVislibParams, CommonVislibParams, ValueAxis } from './types'; import { ColorSchemas, ColorSchemaParams } from '../../charts/public'; -import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; +import { VIS_EVENT_TO_TRIGGER, BaseVisTypeOptions } from '../../visualizations/public'; +import { ValueAxis, ScaleType, AxisType } from '../../vis_type_xy/public'; + +import { HeatmapOptions, getHeatmapCollections } from './editor'; +import { TimeMarker } from './vislib/visualizations/time_marker'; +import { CommonVislibParams, BasicVislibParams, VislibChartType } from './types'; import { toExpressionAst } from './to_ast'; export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaParams { @@ -48,15 +50,15 @@ export const heatmapVisTypeDefinition: BaseVisTypeOptions = { description: i18n.translate('visTypeVislib.heatmap.heatmapDescription', { defaultMessage: 'Shade data in cells in a matrix.', }), - getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter], toExpressionAst, + getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter], visConfig: { defaults: { - type: 'heatmap', + type: VislibChartType.Heatmap, addTooltip: true, addLegend: true, enableHover: false, - legendPosition: Positions.RIGHT, + legendPosition: Position.Right, times: [], colorsNumber: 4, colorSchema: ColorSchemas.Greens, @@ -68,9 +70,9 @@ export const heatmapVisTypeDefinition: BaseVisTypeOptions = { { show: false, id: 'ValueAxis-1', - type: AxisTypes.VALUE, + type: AxisType.Value, scale: { - type: ScaleTypes.LINEAR, + type: ScaleType.Linear, defaultYExtents: false, }, labels: { diff --git a/src/plugins/vis_type_vislib/public/histogram.ts b/src/plugins/vis_type_vislib/public/histogram.ts index de4855ba9aa2b..7424ef5c4df27 100644 --- a/src/plugins/vis_type_vislib/public/histogram.ts +++ b/src/plugins/vis_type_vislib/public/histogram.ts @@ -17,174 +17,14 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { palettes } from '@elastic/eui/lib/services'; -// @ts-ignore -import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; +import { xyVisTypes } from '../../vis_type_xy/public'; +import { BaseVisTypeOptions } from '../../visualizations/public'; -import { AggGroupNames } from '../../data/public'; -import { Schemas } from '../../vis_default_editor/public'; -import { - Positions, - ChartTypes, - ChartModes, - AxisTypes, - ScaleTypes, - AxisModes, - ThresholdLineStyles, - getConfigCollections, -} from './utils/collections'; -import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { Rotates } from '../../charts/public'; -import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; -import { BasicVislibParams } from './types'; import { toExpressionAst } from './to_ast'; +import { BasicVislibParams } from './types'; export const histogramVisTypeDefinition: BaseVisTypeOptions = { - name: 'histogram', - title: i18n.translate('visTypeVislib.histogram.histogramTitle', { - defaultMessage: 'Vertical bar', - }), - icon: 'visBarVertical', - description: i18n.translate('visTypeVislib.histogram.histogramDescription', { - defaultMessage: 'Present data in vertical bars on an axis.', - }), - getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + ...(xyVisTypes.histogram() as BaseVisTypeOptions), toExpressionAst, - visConfig: { - defaults: { - type: 'histogram', - grid: { - categoryLines: false, - }, - categoryAxes: [ - { - id: 'CategoryAxis-1', - type: AxisTypes.CATEGORY, - position: Positions.BOTTOM, - show: true, - style: {}, - scale: { - type: ScaleTypes.LINEAR, - }, - labels: { - show: true, - filter: true, - truncate: 100, - }, - title: {}, - }, - ], - valueAxes: [ - { - id: 'ValueAxis-1', - name: 'LeftAxis-1', - type: AxisTypes.VALUE, - position: Positions.LEFT, - show: true, - style: {}, - scale: { - type: ScaleTypes.LINEAR, - mode: AxisModes.NORMAL, - }, - labels: { - show: true, - rotate: Rotates.HORIZONTAL, - filter: false, - truncate: 100, - }, - title: { - text: countLabel, - }, - }, - ], - seriesParams: [ - { - show: true, - type: ChartTypes.HISTOGRAM, - mode: ChartModes.STACKED, - data: { - label: countLabel, - id: '1', - }, - valueAxis: 'ValueAxis-1', - drawLinesBetweenPoints: true, - lineWidth: 2, - showCircles: true, - }, - ], - addTooltip: true, - addLegend: true, - legendPosition: Positions.RIGHT, - times: [], - addTimeMarker: false, - labels: { - show: false, - }, - thresholdLine: { - show: false, - value: 10, - width: 1, - style: ThresholdLineStyles.FULL, - color: euiPaletteColorBlind()[9], - }, - }, - }, - editorConfig: { - collections: getConfigCollections(), - optionTabs: getAreaOptionTabs(), - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('visTypeVislib.histogram.metricTitle', { - defaultMessage: 'Y-axis', - }), - min: 1, - aggFilter: ['!geo_centroid', '!geo_bounds'], - defaults: [{ schema: 'metric', type: 'count' }], - }, - { - group: AggGroupNames.Metrics, - name: 'radius', - title: i18n.translate('visTypeVislib.histogram.radiusTitle', { - defaultMessage: 'Dot size', - }), - min: 0, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'], - }, - { - group: AggGroupNames.Buckets, - name: 'segment', - title: i18n.translate('visTypeVislib.histogram.segmentTitle', { - defaultMessage: 'X-axis', - }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], - }, - { - group: AggGroupNames.Buckets, - name: 'group', - title: i18n.translate('visTypeVislib.histogram.groupTitle', { - defaultMessage: 'Split series', - }), - min: 0, - max: 3, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], - }, - { - group: AggGroupNames.Buckets, - name: 'split', - title: i18n.translate('visTypeVislib.histogram.splitTitle', { - defaultMessage: 'Split chart', - }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], - }, - ]), - }, + visualization: undefined, }; diff --git a/src/plugins/vis_type_vislib/public/horizontal_bar.ts b/src/plugins/vis_type_vislib/public/horizontal_bar.ts index 144e63224533b..9e919c66cb365 100644 --- a/src/plugins/vis_type_vislib/public/horizontal_bar.ts +++ b/src/plugins/vis_type_vislib/public/horizontal_bar.ts @@ -17,171 +17,14 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { palettes, euiPaletteColorBlind } from '@elastic/eui/lib/services'; +import { xyVisTypes } from '../../vis_type_xy/public'; +import { BaseVisTypeOptions } from '../../visualizations/public'; -import { AggGroupNames } from '../../data/public'; -import { Schemas } from '../../vis_default_editor/public'; -import { - Positions, - ChartTypes, - ChartModes, - AxisTypes, - ScaleTypes, - AxisModes, - ThresholdLineStyles, - getConfigCollections, -} from './utils/collections'; -import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { Rotates } from '../../charts/public'; -import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; -import { BasicVislibParams } from './types'; import { toExpressionAst } from './to_ast'; +import { BasicVislibParams } from './types'; export const horizontalBarVisTypeDefinition: BaseVisTypeOptions = { - name: 'horizontal_bar', - title: i18n.translate('visTypeVislib.horizontalBar.horizontalBarTitle', { - defaultMessage: 'Horizontal bar', - }), - icon: 'visBarHorizontal', - description: i18n.translate('visTypeVislib.horizontalBar.horizontalBarDescription', { - defaultMessage: 'Present data in horizontal bars on an axis.', - }), - getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + ...(xyVisTypes.horizontalBar() as BaseVisTypeOptions), toExpressionAst, - visConfig: { - defaults: { - type: 'histogram', - grid: { - categoryLines: false, - }, - categoryAxes: [ - { - id: 'CategoryAxis-1', - type: AxisTypes.CATEGORY, - position: Positions.LEFT, - show: true, - style: {}, - scale: { - type: ScaleTypes.LINEAR, - }, - labels: { - show: true, - rotate: Rotates.HORIZONTAL, - filter: false, - truncate: 200, - }, - title: {}, - }, - ], - valueAxes: [ - { - id: 'ValueAxis-1', - name: 'LeftAxis-1', - type: AxisTypes.VALUE, - position: Positions.BOTTOM, - show: true, - style: {}, - scale: { - type: ScaleTypes.LINEAR, - mode: AxisModes.NORMAL, - }, - labels: { - show: true, - rotate: Rotates.ANGLED, - filter: true, - truncate: 100, - }, - title: { - text: countLabel, - }, - }, - ], - seriesParams: [ - { - show: true, - type: ChartTypes.HISTOGRAM, - mode: ChartModes.NORMAL, - data: { - label: countLabel, - id: '1', - }, - valueAxis: 'ValueAxis-1', - drawLinesBetweenPoints: true, - lineWidth: 2, - showCircles: true, - }, - ], - addTooltip: true, - addLegend: true, - legendPosition: Positions.RIGHT, - times: [], - addTimeMarker: false, - labels: {}, - thresholdLine: { - show: false, - value: 10, - width: 1, - style: ThresholdLineStyles.FULL, - color: euiPaletteColorBlind()[9], - }, - }, - }, - editorConfig: { - collections: getConfigCollections(), - optionTabs: getAreaOptionTabs(), - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('visTypeVislib.horizontalBar.metricTitle', { - defaultMessage: 'Y-axis', - }), - min: 1, - aggFilter: ['!geo_centroid', '!geo_bounds'], - defaults: [{ schema: 'metric', type: 'count' }], - }, - { - group: AggGroupNames.Metrics, - name: 'radius', - title: i18n.translate('visTypeVislib.horizontalBar.radiusTitle', { - defaultMessage: 'Dot size', - }), - min: 0, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'], - }, - { - group: AggGroupNames.Buckets, - name: 'segment', - title: i18n.translate('visTypeVislib.horizontalBar.segmentTitle', { - defaultMessage: 'X-axis', - }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], - }, - { - group: AggGroupNames.Buckets, - name: 'group', - title: i18n.translate('visTypeVislib.horizontalBar.groupTitle', { - defaultMessage: 'Split series', - }), - min: 0, - max: 3, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], - }, - { - group: AggGroupNames.Buckets, - name: 'split', - title: i18n.translate('visTypeVislib.horizontalBar.splitTitle', { - defaultMessage: 'Split chart', - }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], - }, - ]), - }, + visualization: undefined, }; diff --git a/src/plugins/vis_type_vislib/public/line.ts b/src/plugins/vis_type_vislib/public/line.ts index ffa40c8c29980..15f20edd359dd 100644 --- a/src/plugins/vis_type_vislib/public/line.ts +++ b/src/plugins/vis_type_vislib/public/line.ts @@ -17,164 +17,14 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { palettes, euiPaletteColorBlind } from '@elastic/eui/lib/services'; +import { xyVisTypes } from '../../vis_type_xy/public'; +import { BaseVisTypeOptions } from '../../visualizations/public'; -import { AggGroupNames } from '../../data/public'; -import { Schemas } from '../../vis_default_editor/public'; -import { - Positions, - ChartTypes, - ChartModes, - AxisTypes, - ScaleTypes, - AxisModes, - ThresholdLineStyles, - InterpolationModes, - getConfigCollections, -} from './utils/collections'; -import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { Rotates } from '../../charts/public'; -import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; import { toExpressionAst } from './to_ast'; import { BasicVislibParams } from './types'; export const lineVisTypeDefinition: BaseVisTypeOptions = { - name: 'line', - title: i18n.translate('visTypeVislib.line.lineTitle', { defaultMessage: 'Line' }), - icon: 'visLine', - description: i18n.translate('visTypeVislib.line.lineDescription', { - defaultMessage: 'Display data as a series of points.', - }), - getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + ...(xyVisTypes.line() as BaseVisTypeOptions), toExpressionAst, - visConfig: { - defaults: { - type: 'line', - grid: { - categoryLines: false, - }, - categoryAxes: [ - { - id: 'CategoryAxis-1', - type: AxisTypes.CATEGORY, - position: Positions.BOTTOM, - show: true, - style: {}, - scale: { - type: ScaleTypes.LINEAR, - }, - labels: { - show: true, - filter: true, - truncate: 100, - }, - title: {}, - }, - ], - valueAxes: [ - { - id: 'ValueAxis-1', - name: 'LeftAxis-1', - type: AxisTypes.VALUE, - position: Positions.LEFT, - show: true, - style: {}, - scale: { - type: ScaleTypes.LINEAR, - mode: AxisModes.NORMAL, - }, - labels: { - show: true, - rotate: Rotates.HORIZONTAL, - filter: false, - truncate: 100, - }, - title: { - text: countLabel, - }, - }, - ], - seriesParams: [ - { - show: true, - type: ChartTypes.LINE, - mode: ChartModes.NORMAL, - data: { - label: countLabel, - id: '1', - }, - valueAxis: 'ValueAxis-1', - drawLinesBetweenPoints: true, - lineWidth: 2, - interpolate: InterpolationModes.LINEAR, - showCircles: true, - }, - ], - addTooltip: true, - addLegend: true, - legendPosition: Positions.RIGHT, - times: [], - addTimeMarker: false, - labels: {}, - thresholdLine: { - show: false, - value: 10, - width: 1, - style: ThresholdLineStyles.FULL, - color: euiPaletteColorBlind()[9], - }, - }, - }, - editorConfig: { - collections: getConfigCollections(), - optionTabs: getAreaOptionTabs(), - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('visTypeVislib.line.metricTitle', { defaultMessage: 'Y-axis' }), - min: 1, - aggFilter: ['!geo_centroid', '!geo_bounds'], - defaults: [{ schema: 'metric', type: 'count' }], - }, - { - group: AggGroupNames.Metrics, - name: 'radius', - title: i18n.translate('visTypeVislib.line.radiusTitle', { defaultMessage: 'Dot size' }), - min: 0, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'], - }, - { - group: AggGroupNames.Buckets, - name: 'segment', - title: i18n.translate('visTypeVislib.line.segmentTitle', { defaultMessage: 'X-axis' }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], - }, - { - group: AggGroupNames.Buckets, - name: 'group', - title: i18n.translate('visTypeVislib.line.groupTitle', { - defaultMessage: 'Split series', - }), - min: 0, - max: 3, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], - }, - { - group: AggGroupNames.Buckets, - name: 'split', - title: i18n.translate('visTypeVislib.line.splitTitle', { - defaultMessage: 'Split chart', - }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], - }, - ]), - }, + visualization: undefined, }; diff --git a/src/plugins/vis_type_vislib/public/pie.ts b/src/plugins/vis_type_vislib/public/pie.ts index 41b271054d59f..aa5a3ceaaba98 100644 --- a/src/plugins/vis_type_vislib/public/pie.ts +++ b/src/plugins/vis_type_vislib/public/pie.ts @@ -18,13 +18,15 @@ */ import { i18n } from '@kbn/i18n'; +import { Position } from '@elastic/charts'; import { AggGroupNames } from '../../data/public'; import { Schemas } from '../../vis_default_editor/public'; -import { PieOptions } from './components/options'; -import { getPositions, Positions } from './utils/collections'; +import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; +import { getPositions } from '../../vis_type_xy/public'; + import { CommonVislibParams } from './types'; -import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; +import { PieOptions } from './editor'; import { toExpressionAst } from './to_ast_pie'; export interface PieVisParams extends CommonVislibParams { @@ -52,7 +54,7 @@ export const pieVisTypeDefinition: BaseVisTypeOptions = { type: 'pie', addTooltip: true, addLegend: true, - legendPosition: Positions.RIGHT, + legendPosition: Position.Right, isDonut: true, labels: { show: false, diff --git a/src/plugins/vis_type_vislib/public/pie_fn.ts b/src/plugins/vis_type_vislib/public/pie_fn.ts index 00aa73128c349..8b16d8eb0982f 100644 --- a/src/plugins/vis_type_vislib/public/pie_fn.ts +++ b/src/plugins/vis_type_vislib/public/pie_fn.ts @@ -24,6 +24,7 @@ import { ExpressionFunctionDefinition, Datatable, Render } from '../../expressio // @ts-ignore import { vislibSlicesResponseHandler } from './vislib/response_handler'; import { PieVisParams } from './pie'; +import { VislibChartType } from './types'; import { vislibVisName } from './vis_type_vislib_vis_fn'; export const vislibPieName = 'vislib_pie_vis'; @@ -32,9 +33,9 @@ interface Arguments { visConfig: string; } -interface RenderValue { +export interface PieRenderValue { + visType: Extract; visData: unknown; - visType: string; visConfig: PieVisParams; } @@ -42,7 +43,7 @@ export type VisTypeVislibPieExpressionFunctionDefinition = ExpressionFunctionDef typeof vislibPieName, Datatable, Arguments, - Render + Render >; export const createPieVisFn = (): VisTypeVislibPieExpressionFunctionDefinition => ({ @@ -73,7 +74,7 @@ export const createPieVisFn = (): VisTypeVislibPieExpressionFunctionDefinition = value: { visData, visConfig, - visType: 'pie', + visType: VislibChartType.Pie, }, }; }, diff --git a/src/plugins/vis_type_vislib/public/plugin.ts b/src/plugins/vis_type_vislib/public/plugin.ts index f183042fd5201..36a184d3da507 100644 --- a/src/plugins/vis_type_vislib/public/plugin.ts +++ b/src/plugins/vis_type_vislib/public/plugin.ts @@ -19,25 +19,28 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; -import { VisTypeXyPluginSetup } from 'src/plugins/vis_type_xy/public'; import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; -import { BaseVisTypeOptions, VisualizationsSetup } from '../../visualizations/public'; -import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn'; -import { createPieVisFn } from './pie_fn'; -import { visLibVisTypeDefinitions, pieVisTypeDefinition } from './vis_type_vislib_vis_types'; +import { VisualizationsSetup } from '../../visualizations/public'; import { ChartsPluginSetup } from '../../charts/public'; import { DataPublicPluginStart } from '../../data/public'; import { KibanaLegacyStart } from '../../kibana_legacy/public'; +import { LEGACY_CHARTS_LIBRARY } from '../../vis_type_xy/public'; + +import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn'; +import { createPieVisFn } from './pie_fn'; +import { + convertedTypeDefinitions, + pieVisTypeDefinition, + visLibVisTypeDefinitions, +} from './vis_type_vislib_vis_types'; import { setFormatService, setDataActions } from './services'; import { getVislibVisRenderer } from './vis_renderer'; -import { BasicVislibParams } from './types'; /** @internal */ export interface VisTypeVislibPluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; charts: ChartsPluginSetup; - visTypeXy?: VisTypeXyPluginSetup; } /** @internal */ @@ -56,23 +59,21 @@ export class VisTypeVislibPlugin public async setup( core: VisTypeVislibCoreSetup, - { expressions, visualizations, charts, visTypeXy }: VisTypeVislibPluginSetupDependencies + { expressions, visualizations, charts }: VisTypeVislibPluginSetupDependencies ) { - // if visTypeXy plugin is disabled it's config will be undefined - if (!visTypeXy) { - const convertedTypes: Array> = []; - const convertedFns: any[] = []; - - // Register legacy vislib types that have been converted - convertedFns.forEach(expressions.registerFunction); - convertedTypes.forEach(visualizations.createBaseVisualization); + if (!core.uiSettings.get(LEGACY_CHARTS_LIBRARY, true)) { + // Register only non-replaced vis types + convertedTypeDefinitions.forEach(visualizations.createBaseVisualization); + visualizations.createBaseVisualization(pieVisTypeDefinition); + expressions.registerRenderer(getVislibVisRenderer(core, charts)); + [createVisTypeVislibVisFn(), createPieVisFn()].forEach(expressions.registerFunction); + } else { + // Register all vis types + visLibVisTypeDefinitions.forEach(visualizations.createBaseVisualization); + visualizations.createBaseVisualization(pieVisTypeDefinition); expressions.registerRenderer(getVislibVisRenderer(core, charts)); + [createVisTypeVislibVisFn(), createPieVisFn()].forEach(expressions.registerFunction); } - // Register non-converted types - visLibVisTypeDefinitions.forEach(visualizations.createBaseVisualization); - visualizations.createBaseVisualization(pieVisTypeDefinition); - expressions.registerRenderer(getVislibVisRenderer(core, charts)); - [createVisTypeVislibVisFn(), createPieVisFn()].forEach(expressions.registerFunction); } public start(core: CoreStart, { data }: VisTypeVislibPluginStartDependencies) { diff --git a/src/plugins/vis_type_vislib/public/to_ast.ts b/src/plugins/vis_type_vislib/public/to_ast.ts index 3a05410ff006b..30ed2e4ca1bb4 100644 --- a/src/plugins/vis_type_vislib/public/to_ast.ts +++ b/src/plugins/vis_type_vislib/public/to_ast.ts @@ -21,14 +21,11 @@ import moment from 'moment'; import { VisToExpressionAst, getVisSchemas } from '../../visualizations/public'; import { buildExpression, buildExpressionFunction } from '../../expressions/public'; +import type { Dimensions, DateHistogramParams, HistogramParams } from '../../vis_type_xy/public'; +import { BUCKET_TYPES } from '../../data/public'; import { vislibVisName, VisTypeVislibExpressionFunctionDefinition } from './vis_type_vislib_vis_fn'; -import { BasicVislibParams } from './types'; -import { - DateHistogramParams, - Dimensions, - HistogramParams, -} from './vislib/helpers/point_series/point_series'; +import { BasicVislibParams, VislibChartType } from './types'; import { getEsaggsFn } from './to_ast_esaggs'; export const toExpressionAst: VisToExpressionAst = async (vis, params) => { @@ -47,7 +44,7 @@ export const toExpressionAst: VisToExpressionAst = async (vis if (dimensions.x) { const xAgg = responseAggs[dimensions.x.accessor] as any; - if (xAgg.type.name === 'date_histogram') { + if (xAgg.type.name === BUCKET_TYPES.DATE_HISTOGRAM) { (dimensions.x.params as DateHistogramParams).date = true; const { esUnit, esValue } = xAgg.buckets.getInterval(); (dimensions.x.params as DateHistogramParams).intervalESUnit = esUnit; @@ -57,7 +54,7 @@ export const toExpressionAst: VisToExpressionAst = async (vis .asMilliseconds(); (dimensions.x.params as DateHistogramParams).format = xAgg.buckets.getScaledDateFormat(); (dimensions.x.params as DateHistogramParams).bounds = xAgg.buckets.getBounds(); - } else if (xAgg.type.name === 'histogram') { + } else if (xAgg.type.name === BUCKET_TYPES.HISTOGRAM) { const intervalParam = xAgg.type.paramByName('interval'); const output = { params: {} as any }; await intervalParam.modifyAggConfigOnSearchRequestStart(xAgg, vis.data.searchSource, { @@ -88,15 +85,15 @@ export const toExpressionAst: VisToExpressionAst = async (vis visConfig.dimensions = dimensions; - const visTypeXy = buildExpressionFunction( + const visTypeVislib = buildExpressionFunction( vislibVisName, { - type: vis.type.name, + type: vis.type.name as Exclude, visConfig: JSON.stringify(visConfig), } ); - const ast = buildExpression([getEsaggsFn(vis), visTypeXy]); + const ast = buildExpression([getEsaggsFn(vis), visTypeVislib]); return ast.toAst(); }; diff --git a/src/plugins/vis_type_vislib/public/to_ast_esaggs.ts b/src/plugins/vis_type_vislib/public/to_ast_esaggs.ts index 2835e5cc1c255..d23f1ab3626d3 100644 --- a/src/plugins/vis_type_vislib/public/to_ast_esaggs.ts +++ b/src/plugins/vis_type_vislib/public/to_ast_esaggs.ts @@ -29,6 +29,8 @@ import { BasicVislibParams } from './types'; /** * Get esaggs expressions function + * TODO: replace this with vis.data.aggs!.toExpressionAst(); + * https://github.com/elastic/kibana/issues/61768 * @param vis */ export function getEsaggsFn(vis: Vis | Vis) { diff --git a/src/plugins/vis_type_vislib/public/types.ts b/src/plugins/vis_type_vislib/public/types.ts index c0311edf76154..4f6c427669324 100644 --- a/src/plugins/vis_type_vislib/public/types.ts +++ b/src/plugins/vis_type_vislib/public/types.ts @@ -17,87 +17,71 @@ * under the License. */ -import { TimeMarker } from './vislib/visualizations/time_marker'; +import { $Values } from '@kbn/utility-types'; +import { Position } from '@elastic/charts'; + +import { Labels } from '../../charts/public'; import { - Positions, - ChartModes, - ChartTypes, - AxisModes, - AxisTypes, - InterpolationModes, - ScaleTypes, - ThresholdLineStyles, -} from './utils/collections'; -import { Labels, Style } from '../../charts/public'; -import { Dimensions } from './vislib/helpers/point_series/point_series'; + CategoryAxis, + Dimensions, + Grid, + SeriesParam, + ThresholdLine, + ValueAxis, +} from '../../vis_type_xy/public'; +import { TimeMarker } from './vislib/visualizations/time_marker'; + +/** + * Gauge title alignment + */ +export const Alignment = Object.freeze({ + Automatic: 'automatic' as const, + Horizontal: 'horizontal' as const, + Vertical: 'vertical' as const, +}); +export type Alignment = $Values; + +export const GaugeType = Object.freeze({ + Arc: 'Arc' as const, + Circle: 'Circle' as const, +}); +export type GaugeType = $Values; + +export const VislibChartType = Object.freeze({ + Histogram: 'histogram' as const, + HorizontalBar: 'horizontal_bar' as const, + Line: 'line' as const, + Pie: 'pie' as const, + Area: 'area' as const, + PointSeries: 'point_series' as const, + Heatmap: 'heatmap' as const, + Gauge: 'gauge' as const, + Goal: 'goal' as const, + Metric: 'metric' as const, +}); +export type VislibChartType = $Values; export interface CommonVislibParams { addTooltip: boolean; addLegend: boolean; - legendPosition: Positions; + legendPosition: Position; dimensions: Dimensions; } -export interface Scale { - boundsMargin?: number | ''; - defaultYExtents?: boolean; - max?: number | null; - min?: number | null; - mode?: AxisModes; - setYExtents?: boolean; - type: ScaleTypes; -} - -interface ThresholdLine { - show: boolean; - value: number | null; - width: number | null; - style: ThresholdLineStyles; - color: string; -} - -export interface Axis { - id: string; - labels: Labels; - position: Positions; - scale: Scale; - show: boolean; - style: Style; - title: { text: string }; - type: AxisTypes; -} - -export interface ValueAxis extends Axis { - name: string; -} - -export interface SeriesParam { - data: { label: string; id: string }; - drawLinesBetweenPoints: boolean; - interpolate: InterpolationModes; - lineWidth?: number; - mode: ChartModes; - show: boolean; - showCircles: boolean; - type: ChartTypes; - valueAxis: string; -} - export interface BasicVislibParams extends CommonVislibParams { + type: VislibChartType; + addLegend: boolean; addTimeMarker: boolean; - categoryAxes: Axis[]; + categoryAxes: CategoryAxis[]; orderBucketsBySum?: boolean; labels: Labels; thresholdLine: ThresholdLine; valueAxes: ValueAxis[]; + grid: Grid; gauge?: { percentageMode: boolean; }; - grid: { - categoryLines: boolean; - valueAxis?: string; - }; seriesParams: SeriesParam[]; times: TimeMarker[]; - type: string; + radiusRatio: number; } diff --git a/src/plugins/vis_type_vislib/public/utils/collections.ts b/src/plugins/vis_type_vislib/public/utils/collections.ts deleted file mode 100644 index 44df4864bfd68..0000000000000 --- a/src/plugins/vis_type_vislib/public/utils/collections.ts +++ /dev/null @@ -1,338 +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 { $Values } from '@kbn/utility-types'; - -import { colorSchemas, Rotates } from '../../../charts/public'; - -export const Positions = Object.freeze({ - RIGHT: 'right' as 'right', - LEFT: 'left' as 'left', - TOP: 'top' as 'top', - BOTTOM: 'bottom' as 'bottom', -}); -export type Positions = $Values; - -const getPositions = () => [ - { - text: i18n.translate('visTypeVislib.legendPositions.topText', { - defaultMessage: 'Top', - }), - value: Positions.TOP, - }, - { - text: i18n.translate('visTypeVislib.legendPositions.leftText', { - defaultMessage: 'Left', - }), - value: Positions.LEFT, - }, - { - text: i18n.translate('visTypeVislib.legendPositions.rightText', { - defaultMessage: 'Right', - }), - value: Positions.RIGHT, - }, - { - text: i18n.translate('visTypeVislib.legendPositions.bottomText', { - defaultMessage: 'Bottom', - }), - value: Positions.BOTTOM, - }, -]; - -export const ChartTypes = Object.freeze({ - LINE: 'line' as 'line', - AREA: 'area' as 'area', - HISTOGRAM: 'histogram' as 'histogram', -}); -export type ChartTypes = $Values; - -const getChartTypes = () => [ - { - text: i18n.translate('visTypeVislib.chartTypes.lineText', { - defaultMessage: 'Line', - }), - value: ChartTypes.LINE, - }, - { - text: i18n.translate('visTypeVislib.chartTypes.areaText', { - defaultMessage: 'Area', - }), - value: ChartTypes.AREA, - }, - { - text: i18n.translate('visTypeVislib.chartTypes.barText', { - defaultMessage: 'Bar', - }), - value: ChartTypes.HISTOGRAM, - }, -]; - -export const ChartModes = Object.freeze({ - NORMAL: 'normal' as 'normal', - STACKED: 'stacked' as 'stacked', -}); -export type ChartModes = $Values; - -const getChartModes = () => [ - { - text: i18n.translate('visTypeVislib.chartModes.normalText', { - defaultMessage: 'Normal', - }), - value: ChartModes.NORMAL, - }, - { - text: i18n.translate('visTypeVislib.chartModes.stackedText', { - defaultMessage: 'Stacked', - }), - value: ChartModes.STACKED, - }, -]; - -export const InterpolationModes = Object.freeze({ - LINEAR: 'linear' as 'linear', - CARDINAL: 'cardinal' as 'cardinal', - STEP_AFTER: 'step-after' as 'step-after', -}); -export type InterpolationModes = $Values; - -const getInterpolationModes = () => [ - { - text: i18n.translate('visTypeVislib.interpolationModes.straightText', { - defaultMessage: 'Straight', - }), - value: InterpolationModes.LINEAR, - }, - { - text: i18n.translate('visTypeVislib.interpolationModes.smoothedText', { - defaultMessage: 'Smoothed', - }), - value: InterpolationModes.CARDINAL, - }, - { - text: i18n.translate('visTypeVislib.interpolationModes.steppedText', { - defaultMessage: 'Stepped', - }), - value: InterpolationModes.STEP_AFTER, - }, -]; - -export const AxisTypes = Object.freeze({ - CATEGORY: 'category' as 'category', - VALUE: 'value' as 'value', -}); -export type AxisTypes = $Values; - -export const ScaleTypes = Object.freeze({ - LINEAR: 'linear' as 'linear', - LOG: 'log' as 'log', - SQUARE_ROOT: 'square root' as 'square root', -}); -export type ScaleTypes = $Values; - -const getScaleTypes = () => [ - { - text: i18n.translate('visTypeVislib.scaleTypes.linearText', { - defaultMessage: 'Linear', - }), - value: ScaleTypes.LINEAR, - }, - { - text: i18n.translate('visTypeVislib.scaleTypes.logText', { - defaultMessage: 'Log', - }), - value: ScaleTypes.LOG, - }, - { - text: i18n.translate('visTypeVislib.scaleTypes.squareRootText', { - defaultMessage: 'Square root', - }), - value: ScaleTypes.SQUARE_ROOT, - }, -]; - -export const AxisModes = Object.freeze({ - NORMAL: 'normal' as 'normal', - PERCENTAGE: 'percentage' as 'percentage', - WIGGLE: 'wiggle' as 'wiggle', - SILHOUETTE: 'silhouette' as 'silhouette', -}); -export type AxisModes = $Values; - -const getAxisModes = () => [ - { - text: i18n.translate('visTypeVislib.axisModes.normalText', { - defaultMessage: 'Normal', - }), - value: AxisModes.NORMAL, - }, - { - text: i18n.translate('visTypeVislib.axisModes.percentageText', { - defaultMessage: 'Percentage', - }), - value: AxisModes.PERCENTAGE, - }, - { - text: i18n.translate('visTypeVislib.axisModes.wiggleText', { - defaultMessage: 'Wiggle', - }), - value: AxisModes.WIGGLE, - }, - { - text: i18n.translate('visTypeVislib.axisModes.silhouetteText', { - defaultMessage: 'Silhouette', - }), - value: AxisModes.SILHOUETTE, - }, -]; - -export const ThresholdLineStyles = Object.freeze({ - FULL: 'full' as 'full', - DASHED: 'dashed' as 'dashed', - DOT_DASHED: 'dot-dashed' as 'dot-dashed', -}); -export type ThresholdLineStyles = $Values; - -const getThresholdLineStyles = () => [ - { - value: ThresholdLineStyles.FULL, - text: i18n.translate('visTypeVislib.thresholdLine.style.fullText', { - defaultMessage: 'Full', - }), - }, - { - value: ThresholdLineStyles.DASHED, - text: i18n.translate('visTypeVislib.thresholdLine.style.dashedText', { - defaultMessage: 'Dashed', - }), - }, - { - value: ThresholdLineStyles.DOT_DASHED, - text: i18n.translate('visTypeVislib.thresholdLine.style.dotdashedText', { - defaultMessage: 'Dot-dashed', - }), - }, -]; - -const getRotateOptions = () => [ - { - text: i18n.translate('visTypeVislib.categoryAxis.rotate.horizontalText', { - defaultMessage: 'Horizontal', - }), - value: Rotates.HORIZONTAL, - }, - { - text: i18n.translate('visTypeVislib.categoryAxis.rotate.verticalText', { - defaultMessage: 'Vertical', - }), - value: Rotates.VERTICAL, - }, - { - text: i18n.translate('visTypeVislib.categoryAxis.rotate.angledText', { - defaultMessage: 'Angled', - }), - value: Rotates.ANGLED, - }, -]; - -export const GaugeTypes = Object.freeze({ - ARC: 'Arc' as 'Arc', - CIRCLE: 'Circle' as 'Circle', -}); -export type GaugeTypes = $Values; - -const getGaugeTypes = () => [ - { - text: i18n.translate('visTypeVislib.gauge.gaugeTypes.arcText', { - defaultMessage: 'Arc', - }), - value: GaugeTypes.ARC, - }, - { - text: i18n.translate('visTypeVislib.gauge.gaugeTypes.circleText', { - defaultMessage: 'Circle', - }), - value: GaugeTypes.CIRCLE, - }, -]; - -export const Alignments = Object.freeze({ - AUTOMATIC: 'automatic' as 'automatic', - HORIZONTAL: 'horizontal' as 'horizontal', - VERTICAL: 'vertical' as 'vertical', -}); -export type Alignments = $Values; - -const getAlignments = () => [ - { - text: i18n.translate('visTypeVislib.gauge.alignmentAutomaticTitle', { - defaultMessage: 'Automatic', - }), - value: Alignments.AUTOMATIC, - }, - { - text: i18n.translate('visTypeVislib.gauge.alignmentHorizontalTitle', { - defaultMessage: 'Horizontal', - }), - value: Alignments.HORIZONTAL, - }, - { - text: i18n.translate('visTypeVislib.gauge.alignmentVerticalTitle', { - defaultMessage: 'Vertical', - }), - value: Alignments.VERTICAL, - }, -]; - -const getConfigCollections = () => ({ - legendPositions: getPositions(), - positions: getPositions(), - chartTypes: getChartTypes(), - axisModes: getAxisModes(), - scaleTypes: getScaleTypes(), - chartModes: getChartModes(), - interpolationModes: getInterpolationModes(), - thresholdLineStyles: getThresholdLineStyles(), -}); - -const getGaugeCollections = () => ({ - gaugeTypes: getGaugeTypes(), - alignments: getAlignments(), - colorSchemas, -}); - -const getHeatmapCollections = () => ({ - legendPositions: getPositions(), - scales: getScaleTypes(), - colorSchemas, -}); - -export { - getConfigCollections, - getGaugeCollections, - getHeatmapCollections, - getPositions, - getRotateOptions, - getScaleTypes, - getInterpolationModes, - getChartTypes, - getChartModes, - getAxisModes, -}; diff --git a/src/plugins/vis_type_vislib/public/vis_controller.tsx b/src/plugins/vis_type_vislib/public/vis_controller.tsx index 1804d0d52ae7a..2a32d19874c22 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_renderer.tsx b/src/plugins/vis_type_vislib/public/vis_renderer.tsx index 9c697f481e63e..205c22092ac61 100644 --- a/src/plugins/vis_type_vislib/public/vis_renderer.tsx +++ b/src/plugins/vis_type_vislib/public/vis_renderer.tsx @@ -26,9 +26,13 @@ import { ChartsPluginSetup } from '../../charts/public'; import { VisTypeVislibCoreSetup } from './plugin'; import { VislibRenderValue, vislibVisName } from './vis_type_vislib_vis_fn'; +import { VislibChartType } from './types'; +import { PieRenderValue } from './pie_fn'; -function shouldShowNoResultsMessage(visData: any, visType: string): boolean { - if (['goal', 'gauge'].includes(visType)) { +const VislibWrapper = lazy(() => import('./vis_wrapper')); + +function shouldShowNoResultsMessage(visData: any, visType: VislibChartType): boolean { + if (['goal', 'gauge'].includes(visType as string)) { return false; } @@ -38,13 +42,12 @@ function shouldShowNoResultsMessage(visData: any, visType: string): boolean { return Boolean(isZeroHits); } -const VislibWrapper = lazy(() => import('./vis_wrapper')); - export const getVislibVisRenderer: ( core: VisTypeVislibCoreSetup, charts: ChartsPluginSetup -) => ExpressionRenderDefinition = (core, charts) => ({ +) => ExpressionRenderDefinition = (core, charts) => ({ name: vislibVisName, + displayName: 'Vislib visualization', reuseDomNode: true, render: async (domNode, config, handlers) => { const showNoResult = shouldShowNoResultsMessage(config.visData, config.visType); diff --git a/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts b/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts index dc4a6314fb013..d627b8587882a 100644 --- a/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts +++ b/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts @@ -23,18 +23,18 @@ import { ExpressionFunctionDefinition, Datatable, Render } from '../../expressio // @ts-ignore import { vislibSeriesResponseHandler } from './vislib/response_handler'; -import { BasicVislibParams } from './types'; +import { BasicVislibParams, VislibChartType } from './types'; export const vislibVisName = 'vislib_vis'; interface Arguments { - type: string; + type: Exclude; visConfig: string; } export interface VislibRenderValue { - visData: any; - visType: string; + visType: Exclude; + visData: unknown; visConfig: BasicVislibParams; } @@ -65,7 +65,7 @@ export const createVisTypeVislibVisFn = (): VisTypeVislibExpressionFunctionDefin }, }, fn(context, args, handlers) { - const visType = args.type; + const visType = args.type as Exclude; const visConfig = JSON.parse(args.visConfig) as BasicVislibParams; const visData = vislibSeriesResponseHandler(context, visConfig.dimensions); diff --git a/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_types.ts b/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_types.ts index 1b43a213c618d..28415e3e2fa8c 100644 --- a/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_types.ts +++ b/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_types.ts @@ -36,3 +36,9 @@ export const visLibVisTypeDefinitions = [ gaugeVisTypeDefinition, goalVisTypeDefinition, ]; + +export const convertedTypeDefinitions = [ + heatmapVisTypeDefinition, + gaugeVisTypeDefinition, + goalVisTypeDefinition, +]; diff --git a/src/plugins/vis_type_vislib/public/vis_wrapper.tsx b/src/plugins/vis_type_vislib/public/vis_wrapper.tsx index 980ba1c175885..b8dbd0f945c32 100644 --- a/src/plugins/vis_type_vislib/public/vis_wrapper.tsx +++ b/src/plugins/vis_type_vislib/public/vis_wrapper.tsx @@ -22,15 +22,17 @@ 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'; import { createVislibVisController, VislibVisController } from './vis_controller'; import { VisTypeVislibCoreSetup } from './plugin'; +import { PieRenderValue } from './pie_fn'; import './index.scss'; -type VislibWrapperProps = VislibRenderValue & { +type VislibWrapperProps = (VislibRenderValue | PieRenderValue) & { core: VisTypeVislibCoreSetup; charts: ChartsPluginSetup; handlers: IInterpreterRenderHandlers; @@ -65,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_vislib/public/vislib/components/legend/_legend.scss b/src/plugins/vis_type_vislib/public/vislib/components/legend/_legend.scss index a06f0cb00787b..cb63811800c2d 100644 --- a/src/plugins/vis_type_vislib/public/vislib/components/legend/_legend.scss +++ b/src/plugins/vis_type_vislib/public/vislib/components/legend/_legend.scss @@ -3,8 +3,6 @@ // NOTE: Some of the styles attempt to align with the TSVB legend $visLegendWidth: 150px; -$visColorPickerWidth: $euiSizeM * 10; -$visLegendLineHeight: $euiSize; .visLegend__toggle { border-radius: $euiBorderRadius; @@ -81,20 +79,3 @@ $visLegendLineHeight: $euiSize; visibility: hidden; } } - -.visLegend__valueColorPicker { - width: ($euiSizeL * 8); // 8 columns -} - -.visLegend__valueColorPickerDot { - cursor: pointer; - - &:hover { - transform: scale(1.4); - } - - &-isSelected { - border: $euiSizeXS solid; - border-radius: 100%; - } -} diff --git a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx index 7acc97404c11c..5b1a2b3975367 100644 --- a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx +++ b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx @@ -243,7 +243,7 @@ describe('VisLegend Component', () => { const first = getLegendItems(wrapper).first(); first.simulate('click'); - expect(wrapper.exists('.visLegend__valueDetails')).toBe(true); + expect(wrapper.exists('.visColorPicker')).toBe(true); }); }); @@ -256,8 +256,8 @@ describe('VisLegend Component', () => { const first = getLegendItems(wrapper).first(); first.simulate('click'); - const popover = wrapper.find('.visLegend__valueDetails').first(); - const firstColor = popover.find('.visLegend__valueColorPickerDot').first(); + const popover = wrapper.find('.visColorPicker').first(); + const firstColor = popover.find('.visColorPicker__valueDot').first(); firstColor.simulate('click'); const colors = mockState.get('vis.colors'); diff --git a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx index cec97f0cadf11..5065642d88c6b 100644 --- a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx +++ b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx @@ -78,18 +78,20 @@ export class VisLegend extends PureComponent { }); }; - setColor = (label: string, color: string) => (event: BaseSyntheticEvent) => { + setColor = (label: string | number, color: string | null, event: BaseSyntheticEvent) => { if ((event as KeyboardEvent).key && (event as KeyboardEvent).key !== keys.ENTER) { return; } - const colors = this.props.uiState?.get('vis.colors') || {}; - if (colors[label] === color) delete colors[label]; - else colors[label] = color; - this.props.uiState?.setSilent('vis.colors', null); - this.props.uiState?.set('vis.colors', colors); - this.props.uiState?.emit('colorChanged'); - this.refresh(); + this.setState({ selectedLabel: null }, () => { + const colors = this.props.uiState?.get('vis.colors') || {}; + if (colors[label] === color || !color) delete colors[label]; + else colors[label] = color; + this.props.uiState?.setSilent('vis.colors', null); + this.props.uiState?.set('vis.colors', colors); + this.props.uiState?.emit('colorChanged'); + this.refresh(); + }); }; filter = ({ values: data }: LegendItem, negate: boolean) => { diff --git a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx index 39e6fb2d2aff4..6c7e343a22d8f 100644 --- a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx +++ b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx @@ -18,10 +18,8 @@ */ import React, { memo, useState, BaseSyntheticEvent, KeyboardEvent } from 'react'; -import classNames from 'classnames'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPopover, keys, @@ -33,7 +31,8 @@ import { EuiButtonGroupOptionProps, } from '@elastic/eui'; -import { legendColors, LegendItem } from './models'; +import { LegendItem } from './models'; +import { ColorPicker } from '../../../../../charts/public'; interface Props { item: LegendItem; @@ -45,7 +44,7 @@ interface Props { onSelect: (label: string | null) => (event?: BaseSyntheticEvent) => void; onHighlight: (event: BaseSyntheticEvent) => void; onUnhighlight: (event: BaseSyntheticEvent) => void; - setColor: (label: string, color: string) => (event: BaseSyntheticEvent) => void; + setColor: (label: string, color: string | null, event: BaseSyntheticEvent) => void; getColor: (label: string) => string; } @@ -159,40 +158,14 @@ const VisLegendItemComponent = ({ closePopover={onSelect(null)} panelPaddingSize="s" > -
- {canFilter && renderFilterBar()} + {canFilter && renderFilterBar()} -
- - - - {legendColors.map((color) => ( - - ))} -
-
+ setColor(item.label, c, e)} + /> ); diff --git a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.test.ts b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.test.ts index ae6b365efc0cd..1b51fb8a53d11 100644 --- a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.test.ts +++ b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.test.ts @@ -17,9 +17,10 @@ * under the License. */ +import type { Dimension } from '../../../../../vis_type_xy/public'; + import { addToSiri, Serie } from './_add_to_siri'; import { Point } from './_get_point'; -import { Dimension } from './point_series'; describe('addToSiri', function () { it('creates a new series the first time it sees an id', function () { diff --git a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.ts b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.ts index 5e5185d6c31ab..2e04e916e85f2 100644 --- a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.ts +++ b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.ts @@ -17,8 +17,10 @@ * under the License. */ +import { getAggId } from '../../../../../vis_type_xy/public'; +import type { Dimension } from '../../../../../vis_type_xy/public'; + import { Point } from './_get_point'; -import { Dimension } from './point_series'; export interface Serie { id: string; @@ -48,7 +50,7 @@ export function addToSiri( } series.set(id, { - id: id.split('-').pop() as string, + id: getAggId(id), rawId: id, label: yLabel == null ? id : yLabel, count: 0, diff --git a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.test.ts b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.test.ts index fb14b04357f8f..5efde63427573 100644 --- a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.test.ts +++ b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.test.ts @@ -17,8 +17,10 @@ * under the License. */ +import type { Dimension, Dimensions } from '../../../../../vis_type_xy/public'; + import { getAspects } from './_get_aspects'; -import { Dimension, Dimensions, Aspect } from './point_series'; +import { Aspect } from './point_series'; import { Table, Row } from '../../types'; describe('getAspects', function () { diff --git a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.ts b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.ts index 87819aa9b2a5c..e4094a4549841 100644 --- a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.ts +++ b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.ts @@ -17,8 +17,10 @@ * under the License. */ +import type { Dimensions } from '../../../../../vis_type_xy/public'; + import { makeFakeXAspect } from './_fake_x_aspect'; -import { Dimensions, Aspects } from './point_series'; +import { Aspects } from './point_series'; import { Table } from '../../types'; /** diff --git a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.test.ts b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.test.ts index be44975bd4eb0..03d41096a8c6f 100644 --- a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.test.ts +++ b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.test.ts @@ -18,6 +18,7 @@ */ import { IFieldFormatsRegistry } from '../../../../../data/common'; + import { getPoint } from './_get_point'; import { setFormatService } from '../../../services'; import { Aspect } from './point_series'; @@ -94,7 +95,12 @@ describe('getPoint', function () { it('should call deserialize', function () { const seriesAspect = [ - { accessor: '1', format: { id: 'number', params: { pattern: '$' } } } as Aspect, + { + title: 'series', + accessor: '1', + format: { id: 'number', params: { pattern: '$' } }, + params: {}, + } as Aspect, ]; getPoint(table, xAspect, seriesAspect, row, 0, yAspect); diff --git a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.test.ts b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.test.ts index 3f0560c2c9f28..aa24530b025dc 100644 --- a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.test.ts +++ b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.test.ts @@ -18,16 +18,12 @@ */ import moment from 'moment'; + +import type { DateHistogramParams, HistogramParams } from '../../../../../vis_type_xy/public'; + import { initXAxis } from './_init_x_axis'; import { makeFakeXAspect } from './_fake_x_aspect'; -import { - Aspects, - Chart, - DateHistogramOrdered, - DateHistogramParams, - HistogramOrdered, - HistogramParams, -} from './point_series'; +import { Aspects, Chart, DateHistogramOrdered, HistogramOrdered } from './point_series'; import { Table, Column } from '../../types'; describe('initXAxis', function () { @@ -110,7 +106,7 @@ describe('initXAxis', function () { it('reads the date interval param from the x agg', function () { const dateHistogramParams = chart.aspects.x[0].params as DateHistogramParams; - dateHistogramParams.interval = 'P1D'; + dateHistogramParams.interval = moment.duration(1, 'd').asMilliseconds(); dateHistogramParams.intervalESValue = 1; dateHistogramParams.intervalESUnit = 'd'; dateHistogramParams.date = true; diff --git a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.test.ts b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.test.ts index b2e4d6e4b40c9..7dd1dd259f785 100644 --- a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.test.ts +++ b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.test.ts @@ -19,8 +19,11 @@ import moment from 'moment'; import _ from 'lodash'; + +import type { DateHistogramParams } from '../../../../../vis_type_xy/public/types'; + import { orderedDateAxis } from './_ordered_date_axis'; -import { DateHistogramParams, OrderedChart } from './point_series'; +import { OrderedChart } from './point_series'; describe('orderedDateAxis', function () { const baseArgs = { diff --git a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.test.ts b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.test.ts index 2a5ab197c9ab7..7c343af7840ee 100644 --- a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.test.ts +++ b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.test.ts @@ -18,7 +18,10 @@ */ import _ from 'lodash'; -import { buildPointSeriesData, Dimensions } from './point_series'; + +import type { Dimensions } from '../../../../../vis_type_xy/public'; + +import { buildPointSeriesData } from './point_series'; import { Table, Column } from '../../types'; import { setFormatService } from '../../../services'; import { Serie } from './_add_to_siri'; diff --git a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts index f40d01e6a8123..2bc669e0b77a3 100644 --- a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts +++ b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts @@ -18,6 +18,14 @@ */ import { Duration } from 'moment'; + +import type { + Dimension, + Dimensions, + DateHistogramParams, + HistogramParams, +} from '../../../../../vis_type_xy/public'; + import { getSeries } from './_get_series'; import { getAspects } from './_get_aspects'; import { initYAxis } from './_init_y_axis'; @@ -26,41 +34,6 @@ import { orderedDateAxis } from './_ordered_date_axis'; import { Serie } from './_add_to_siri'; import { Column, Table } from '../../types'; -export interface DateHistogramParams { - date: boolean; - interval: number | string; - intervalESValue: number; - intervalESUnit: string; - format: string; - bounds?: { - min: string | number; - max: string | number; - }; -} -export interface HistogramParams { - interval: number; -} -export interface FakeParams { - defaultValue: string; -} -export interface Dimension { - accessor: number; - format: { - id?: string; - params?: { pattern?: string; [key: string]: any }; - }; - params: DateHistogramParams | HistogramParams | FakeParams | {}; -} - -export interface Dimensions { - x: Dimension | null; - y: Dimension[]; - z?: Dimension[]; - series?: Dimension | Dimension[]; - width?: Dimension[]; - splitRow?: Dimension[]; - splitColumn?: Dimension[]; -} export interface Aspect { accessor: Column['id']; column?: Dimension['accessor']; diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/handler.js b/src/plugins/vis_type_vislib/public/vislib/lib/handler.js index 938ea3adcb9b5..e1908a8483feb 100644 --- a/src/plugins/vis_type_vislib/public/vislib/lib/handler.js +++ b/src/plugins/vis_type_vislib/public/vislib/lib/handler.js @@ -21,15 +21,16 @@ import d3 from 'd3'; import _ from 'lodash'; import MarkdownIt from 'markdown-it'; +import { dispatchRenderComplete } from '../../../../kibana_utils/public'; + +import { visTypes as chartTypes } from '../visualizations/vis_types'; import { NoResults } from '../errors'; import { Layout } from './layout/layout'; import { ChartTitle } from './chart_title'; import { Alerts } from './alerts'; import { Axis } from './axis/axis'; import { ChartGrid as Grid } from './chart_grid'; -import { visTypes as chartTypes } from '../visualizations/vis_types'; import { Binder } from './binder'; -import { dispatchRenderComplete } from '../../../../kibana_utils/public'; const markdownIt = new MarkdownIt({ html: false, diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile.json b/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile.json index d52cb18727c05..50d6eab03e3f7 100644 --- a/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile.json +++ b/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile.json @@ -31,7 +31,7 @@ }, "params": { "date": true, - "interval": "P1D", + "interval": 86400000, "format": "YYYY-MM-DD", "bounds": { "min": "2019-05-10T04:00:00.000Z", @@ -128,7 +128,7 @@ }, "xAxisLabel": "timestamp per day", "ordered": { - "interval": "P1D", + "interval": 86400000, "date": true, "min": 1557460800000, "max": 1557656337342 diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value.json b/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value.json index 6e1a707229974..1987c59f6722b 100644 --- a/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value.json +++ b/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value.json @@ -31,7 +31,7 @@ }, "params": { "date": true, - "interval": "P1D", + "interval": 86400000, "format": "YYYY-MM-DD", "bounds": { "min": "2019-05-10T04:00:00.000Z", @@ -128,7 +128,7 @@ }, "xAxisLabel": "timestamp per day", "ordered": { - "interval": "P1D", + "interval": 86400000, "date": true, "min": 1557460800000, "max": 1557656337342 @@ -460,4 +460,4 @@ "50th percentile of AvgTicketPrice" ] } -} \ No newline at end of file +} diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value_result.json b/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value_result.json index f7dd18f5eb712..ae1f3cbf24c33 100644 --- a/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value_result.json +++ b/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value_result.json @@ -32,7 +32,7 @@ }, "params": { "date": true, - "interval": "P1D", + "interval": 86400000, "format": "YYYY-MM-DD", "bounds": { "min": "2019-05-10T04:00:00.000Z", @@ -453,4 +453,4 @@ } ], "enableHover": true -} \ No newline at end of file +} diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile_result.json b/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile_result.json index 02062c987564e..f2ee245a8431f 100644 --- a/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile_result.json +++ b/src/plugins/vis_type_vislib/public/vislib/lib/types/testdata_linechart_percentile_result.json @@ -32,7 +32,7 @@ }, "params": { "date": true, - "interval": "P1D", + "interval": 86400000, "format": "YYYY-MM-DD", "bounds": { "min": "2019-05-10T04:00:00.000Z", @@ -455,4 +455,4 @@ } ], "enableHover": true -} \ No newline at end of file +} diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.js index a2b830ffaa781..40d15733b418d 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.js @@ -22,8 +22,8 @@ import { Chart } from './_chart'; import { gaugeTypes } from './gauges/gauge_types'; export class GaugeChart extends Chart { - constructor(handler, chartEl, chartData, deps) { - super(handler, chartEl, chartData, deps); + constructor(handler, chartEl, chartData, uiSettings) { + super(handler, chartEl, chartData, uiSettings); this.gaugeConfig = handler.visConfig.get('gauge', {}); this.gauge = new gaugeTypes[this.gaugeConfig.type](this); } diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/heatmap_chart.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/heatmap_chart.js index 4564721464767..d725c29eef310 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/heatmap_chart.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/heatmap_chart.js @@ -40,8 +40,8 @@ const defaults = { * @param chartData {Object} Elasticsearch query results for this specific chart */ export class HeatmapChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs, deps) { - super(handler, chartEl, chartData, seriesConfigArgs, deps); + constructor(handler, chartEl, chartData, seriesConfigArgs, core) { + super(handler, chartEl, chartData, seriesConfigArgs, core); this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); diff --git a/src/plugins/vis_type_vislib/server/ui_settings.ts b/src/plugins/vis_type_vislib/server/ui_settings.ts index a48cbbae3d0ca..58564b4055daa 100644 --- a/src/plugins/vis_type_vislib/server/ui_settings.ts +++ b/src/plugins/vis_type_vislib/server/ui_settings.ts @@ -24,6 +24,8 @@ import { UiSettingsParams } from 'kibana/server'; import { DIMMING_OPACITY_SETTING, HEATMAP_MAX_BUCKETS_SETTING } from '../common'; export const uiSettings: Record = { + // TODO: move this to vis_type_xy when vislib is removed + // https://github.com/elastic/kibana/issues/56143 [DIMMING_OPACITY_SETTING]: { name: i18n.translate('visTypeVislib.advancedSettings.visualization.dimmingOpacityTitle', { defaultMessage: 'Dimming opacity', diff --git a/src/plugins/vis_type_xy/README.md b/src/plugins/vis_type_xy/README.md index 70ddb21c1e9db..549fc4e3ea189 100644 --- a/src/plugins/vis_type_xy/README.md +++ b/src/plugins/vis_type_xy/README.md @@ -1,2 +1,2 @@ Contains the new xy-axis chart using the elastic-charts library, which will eventually -replace the vislib xy-axis (bar, area, line) charts. \ No newline at end of file +replace the vislib xy-axis charts including bar, area, and line. diff --git a/src/plugins/vis_type_xy/common/index.ts b/src/plugins/vis_type_xy/common/index.ts new file mode 100644 index 0000000000000..edee1ea3219db --- /dev/null +++ b/src/plugins/vis_type_xy/common/index.ts @@ -0,0 +1,37 @@ +/* + * 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 { $Values } from '@kbn/utility-types'; + +/** + * Type of charts able to render + */ +export const ChartType = Object.freeze({ + Line: 'line' as const, + Area: 'area' as const, + Histogram: 'histogram' as const, +}); +export type ChartType = $Values; + +/** + * Type of xy visualizations + */ +export type XyVisType = ChartType | 'horizontal_bar'; + +export const LEGACY_CHARTS_LIBRARY = 'visualization:visualize:legacyChartsLibrary'; diff --git a/src/plugins/ui_actions/public/triggers/select_range_trigger.ts b/src/plugins/vis_type_xy/jest.config.js similarity index 62% rename from src/plugins/ui_actions/public/triggers/select_range_trigger.ts rename to src/plugins/vis_type_xy/jest.config.js index 312e75314bd92..556e518d4f4e1 100644 --- a/src/plugins/ui_actions/public/triggers/select_range_trigger.ts +++ b/src/plugins/vis_type_xy/jest.config.js @@ -17,16 +17,8 @@ * 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', - }), +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/vis_type_xy'], }; diff --git a/src/plugins/vis_type_xy/kibana.json b/src/plugins/vis_type_xy/kibana.json index ca02da45e9112..14c3ce36bf375 100644 --- a/src/plugins/vis_type_xy/kibana.json +++ b/src/plugins/vis_type_xy/kibana.json @@ -3,5 +3,6 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": ["charts", "expressions", "visualizations"] + "requiredPlugins": ["charts", "data", "expressions", "visualizations"], + "requiredBundles": ["kibanaUtils", "visDefaultEditor"] } diff --git a/src/plugins/vis_type_xy/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/vis_type_xy/public/__snapshots__/to_ast.test.ts.snap new file mode 100644 index 0000000000000..e6665c26a2815 --- /dev/null +++ b/src/plugins/vis_type_xy/public/__snapshots__/to_ast.test.ts.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`xy vis toExpressionAst function should match basic snapshot 1`] = ` +Object { + "addArgument": [Function], + "arguments": Object { + "type": Array [ + "area", + ], + "visConfig": Array [ + "{\\"type\\":\\"area\\",\\"grid\\":{\\"categoryLines\\":false,\\"style\\":{\\"color\\":\\"#eee\\"}},\\"categoryAxes\\":[{\\"id\\":\\"CategoryAxis-1\\",\\"type\\":\\"category\\",\\"position\\":\\"bottom\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\"},\\"labels\\":{\\"show\\":true,\\"truncate\\":100},\\"title\\":{}}],\\"valueAxes\\":[{\\"id\\":\\"ValueAxis-1\\",\\"name\\":\\"LeftAxis-1\\",\\"type\\":\\"value\\",\\"position\\":\\"left\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\",\\"mode\\":\\"normal\\"},\\"labels\\":{\\"show\\":true,\\"rotate\\":0,\\"filter\\":false,\\"truncate\\":100},\\"title\\":{\\"text\\":\\"Sum of total_quantity\\"}}],\\"seriesParams\\":[{\\"show\\":\\"true\\",\\"type\\":\\"area\\",\\"mode\\":\\"stacked\\",\\"data\\":{\\"label\\":\\"Sum of total_quantity\\",\\"id\\":\\"1\\"},\\"drawLinesBetweenPoints\\":true,\\"showCircles\\":true,\\"interpolate\\":\\"linear\\",\\"valueAxis\\":\\"ValueAxis-1\\"}],\\"addTooltip\\":true,\\"addLegend\\":true,\\"legendPosition\\":\\"top\\",\\"times\\":[],\\"addTimeMarker\\":false,\\"thresholdLine\\":{\\"show\\":false,\\"value\\":10,\\"width\\":1,\\"style\\":\\"full\\",\\"color\\":\\"#E7664C\\"},\\"labels\\":{},\\"dimensions\\":{\\"x\\":{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"date\\",\\"params\\":{\\"pattern\\":\\"HH:mm:ss.SSS\\"}},\\"params\\":{}},\\"y\\":[{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\",\\"params\\":{\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}],\\"series\\":[{\\"accessor\\":2,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}]}}", + ], + }, + "getArgument": [Function], + "name": "xy_vis", + "removeArgument": [Function], + "replaceArgument": [Function], + "toAst": [Function], + "toString": [Function], + "type": "expression_function_builder", +} +`; diff --git a/src/plugins/vis_type_xy/public/_chart.scss b/src/plugins/vis_type_xy/public/_chart.scss new file mode 100644 index 0000000000000..ac9d4ed04aec4 --- /dev/null +++ b/src/plugins/vis_type_xy/public/_chart.scss @@ -0,0 +1,7 @@ +.xyChart__container { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} diff --git a/src/plugins/vis_type_xy/public/components/_detailed_tooltip.scss b/src/plugins/vis_type_xy/public/components/_detailed_tooltip.scss new file mode 100644 index 0000000000000..91b0a8d023290 --- /dev/null +++ b/src/plugins/vis_type_xy/public/components/_detailed_tooltip.scss @@ -0,0 +1,34 @@ +.detailedTooltip { + @include euiToolTipStyle('s'); + pointer-events: none; + max-width: $euiSizeXL * 10; + overflow: hidden; + padding: $euiSizeS; + + table { + td, + th { + text-align: left; + padding: $euiSizeXS; + overflow-wrap: break-word; + word-wrap: break-word; + } + } +} + +.detailedTooltip__header { + > :last-child { + margin-bottom: $euiSizeS; + } +} + +.detailedTooltip__labelContainer, +.detailedTooltip__valueContainer { + overflow-wrap: break-word; + word-wrap: break-word; +} + +.detailedTooltip__label { + font-weight: $euiFontWeightMedium; + color: shade($euiColorGhost, 20%); +} diff --git a/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx b/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx new file mode 100644 index 0000000000000..3427baed41b8d --- /dev/null +++ b/src/plugins/vis_type_xy/public/components/detailed_tooltip.tsx @@ -0,0 +1,142 @@ +/* + * 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 React from 'react'; +import { isNil } from 'lodash'; + +import { + CustomTooltip, + TooltipValue, + TooltipValueFormatter, + XYChartSeriesIdentifier, +} from '@elastic/charts'; + +import { BUCKET_TYPES } from '../../../data/public'; + +import { Aspects } from '../types'; + +import './_detailed_tooltip.scss'; +import { fillEmptyValue } from '../utils/get_series_name_fn'; +import { COMPLEX_SPLIT_ACCESSOR } from '../utils/accessors'; + +interface TooltipData { + label: string; + value: string; +} + +const getTooltipData = ( + aspects: Aspects, + header: TooltipValue | null, + value: TooltipValue +): TooltipData[] => { + const data: TooltipData[] = []; + + if (header) { + const xFormatter = + aspects.x.aggType === BUCKET_TYPES.DATE_RANGE || aspects.x.aggType === BUCKET_TYPES.RANGE + ? null + : aspects.x.formatter; + data.push({ + label: aspects.x.title, + value: xFormatter ? xFormatter(header.value) : `${header.value}`, + }); + } + + const valueSeries = value.seriesIdentifier as XYChartSeriesIdentifier; + const yAccessor = aspects.y.find(({ accessor }) => accessor === valueSeries.yAccessor) ?? null; + + if (yAccessor) { + data.push({ + label: yAccessor.title, + value: yAccessor.formatter ? yAccessor.formatter(value.value) : `${value.value}`, + }); + } + + if (aspects.z && !isNil(value.markValue)) { + data.push({ + label: aspects.z.title, + value: aspects.z.formatter ? aspects.z.formatter(value.markValue) : `${value.markValue}`, + }); + } + + valueSeries.splitAccessors.forEach((splitValue, key) => { + const split = (aspects.series ?? []).find(({ accessor }, i) => { + return accessor === key || key === `${COMPLEX_SPLIT_ACCESSOR}::${i}`; + }); + + if (split) { + data.push({ + label: split?.title, + value: + split?.formatter && !key.toString().startsWith(COMPLEX_SPLIT_ACCESSOR) + ? split?.formatter(splitValue) + : `${splitValue}`, + }); + } + }); + + return data; +}; + +const renderData = ({ label, value: rawValue }: TooltipData, index: number) => { + const value = fillEmptyValue(rawValue); + return label && value ? ( + + +
{label}
+ + + +
{value}
+ + + ) : null; +}; + +export const getDetailedTooltip = (aspects: Aspects) => ( + headerFormatter?: TooltipValueFormatter +): CustomTooltip => { + return function DetailedTooltip({ header, values }) { + // Note: first value is not necessarily the closest value + // To be fixed with https://github.com/elastic/elastic-charts/issues/835 + // TODO: Allow multiple values to be displayed in tooltip + const highlightedValue = values.find(({ isHighlighted }) => isHighlighted); + + if (!highlightedValue) { + return null; + } + + const tooltipData = getTooltipData(aspects, header, highlightedValue); + + if (tooltipData.length === 0) { + return null; + } + + return ( +
+ {headerFormatter && header && ( +
{headerFormatter(header)}
+ )} + + {tooltipData.map(renderData)} +
+
+ ); + }; +}; diff --git a/src/plugins/vis_type_xy/public/components/index.ts b/src/plugins/vis_type_xy/public/components/index.ts new file mode 100644 index 0000000000000..d8d55c77b7a8a --- /dev/null +++ b/src/plugins/vis_type_xy/public/components/index.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +export { XYAxis } from './xy_axis'; +export { XYEndzones } from './xy_endzones'; +export { XYCurrentTime } from './xy_current_time'; +export { XYSettings } from './xy_settings'; +export { XYThresholdLine } from './xy_threshold_line'; +export { SplitChartWarning } from './split_chart_warning'; 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 new file mode 100644 index 0000000000000..7265e70a791a3 --- /dev/null +++ b/src/plugins/vis_type_xy/public/components/split_chart_warning.tsx @@ -0,0 +1,55 @@ +/* + * 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 React, { FC } from 'react'; + +import { EuiLink, EuiCallOut } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { getDocLinks } from '../services'; + +export const SplitChartWarning: FC = () => { + const advancedSettingsLink = getDocLinks().links.management.visualizationSettings; + + return ( + + + + + ), + }} + /> + + ); +}; diff --git a/src/plugins/vis_type_xy/public/components/xy_axis.tsx b/src/plugins/vis_type_xy/public/components/xy_axis.tsx new file mode 100644 index 0000000000000..b64f2bf1ec0ec --- /dev/null +++ b/src/plugins/vis_type_xy/public/components/xy_axis.tsx @@ -0,0 +1,55 @@ +/* + * 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 React, { FC } from 'react'; + +import { Axis } from '@elastic/charts'; + +import { AxisConfig } from '../types'; + +type XYAxisPros = AxisConfig; + +export const XYAxis: FC = ({ + id, + title, + show, + position, + groupId, + grid, + ticks, + domain, + style, + integersOnly, +}) => ( + +); diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/__mocks__/@elastic/charts.js b/src/plugins/vis_type_xy/public/components/xy_current_time.tsx similarity index 61% rename from src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/__mocks__/@elastic/charts.js rename to src/plugins/vis_type_xy/public/components/xy_current_time.tsx index 19bfe685cac90..66ad4fda3bec7 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/__mocks__/@elastic/charts.js +++ b/src/plugins/vis_type_xy/public/components/xy_current_time.tsx @@ -17,28 +17,21 @@ * under the License. */ -export const CurveType = { - CURVE_CARDINAL: 0, - CURVE_NATURAL: 1, - CURVE_MONOTONE_X: 2, - CURVE_MONOTONE_Y: 3, - CURVE_BASIS: 4, - CURVE_CATMULL_ROM: 5, - CURVE_STEP: 6, - CURVE_STEP_AFTER: 7, - CURVE_STEP_BEFORE: 8, - LINEAR: 9, -}; +import React, { FC } from 'react'; +import { DomainRange } from '@elastic/charts'; +import { CurrentTime } from '../../../charts/public'; -export const ScaleType = { - Linear: 'linear', - Ordinal: 'ordinal', - Log: 'log', - Sqrt: 'sqrt', - Time: 'time', -}; +interface XYCurrentTime { + enabled: boolean; + isDarkMode: boolean; + domain?: DomainRange; +} -export const BarSeries = () => null; -export const AreaSeries = () => null; +export const XYCurrentTime: FC = ({ enabled, isDarkMode, domain }) => { + if (!enabled) { + return null; + } -export { LIGHT_THEME, DARK_THEME } from '@elastic/charts'; + const domainEnd = domain && 'max' in domain ? domain.max : undefined; + return ; +}; diff --git a/src/plugins/vis_type_xy/public/components/xy_endzones.tsx b/src/plugins/vis_type_xy/public/components/xy_endzones.tsx new file mode 100644 index 0000000000000..33e1d1e18bb1d --- /dev/null +++ b/src/plugins/vis_type_xy/public/components/xy_endzones.tsx @@ -0,0 +1,68 @@ +/* + * 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 React, { FC } from 'react'; + +import { DomainRange } from '@elastic/charts'; + +import { Endzones } from '../../../charts/public'; + +interface XYEndzones { + enabled: boolean; + isDarkMode: boolean; + isFullBin: boolean; + hideTooltips?: boolean; + domain?: DomainRange; + adjustedDomain?: DomainRange; +} + +export const XYEndzones: FC = ({ + enabled, + isDarkMode, + isFullBin, + hideTooltips, + domain, + adjustedDomain, +}) => { + if ( + enabled && + domain && + adjustedDomain && + 'min' in domain && + 'max' in domain && + domain.minInterval !== undefined && + 'min' in adjustedDomain && + 'max' in adjustedDomain + ) { + return ( + + ); + } + + return null; +}; diff --git a/src/plugins/vis_type_xy/public/components/xy_settings.tsx b/src/plugins/vis_type_xy/public/components/xy_settings.tsx new file mode 100644 index 0000000000000..3682fdf3350b0 --- /dev/null +++ b/src/plugins/vis_type_xy/public/components/xy_settings.tsx @@ -0,0 +1,182 @@ +/* + * 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 React, { FC } from 'react'; + +import { + Direction, + Settings, + DomainRange, + Position, + PartialTheme, + ElementClickListener, + BrushEndListener, + RenderChangeListener, + LegendAction, + LegendColorPicker, + TooltipProps, + TickFormatter, +} from '@elastic/charts'; + +import { renderEndzoneTooltip } from '../../../charts/public'; + +import { getThemeService, getUISettings } from '../services'; +import { VisConfig } from '../types'; +import { fillEmptyValue } from '../utils/get_series_name_fn'; + +declare global { + interface Window { + /** + * Flag used to enable debugState on elastic charts + */ + _echDebugStateFlag?: boolean; + } +} + +type XYSettingsProps = Pick< + VisConfig, + | 'markSizeRatio' + | 'rotation' + | 'enableHistogramMode' + | 'tooltip' + | 'isTimeChart' + | 'xAxis' + | 'orderBucketsBySum' +> & { + xDomain?: DomainRange; + adjustedXDomain?: DomainRange; + showLegend: boolean; + onElementClick: ElementClickListener; + onBrushEnd?: BrushEndListener; + onRenderChange: RenderChangeListener; + legendAction?: LegendAction; + legendColorPicker: LegendColorPicker; + legendPosition: Position; +}; + +export const XYSettings: FC = ({ + markSizeRatio, + rotation, + enableHistogramMode, + tooltip, + isTimeChart, + xAxis, + orderBucketsBySum, + xDomain, + adjustedXDomain, + showLegend, + onElementClick, + onBrushEnd, + onRenderChange, + legendAction, + legendColorPicker, + legendPosition, +}) => { + const themeService = getThemeService(); + const theme = themeService.useChartsTheme(); + const baseTheme = themeService.useChartsBaseTheme(); + const dimmingOpacity = getUISettings().get('visualization:dimmingOpacity'); + const fontSize = + typeof theme.barSeriesStyle?.displayValue?.fontSize === 'number' + ? { min: theme.barSeriesStyle?.displayValue?.fontSize } + : theme.barSeriesStyle?.displayValue?.fontSize ?? { min: 8 }; + + const themeOverrides: PartialTheme = { + markSizeRatio, + sharedStyle: { + unhighlighted: { + opacity: dimmingOpacity, + }, + }, + barSeriesStyle: { + displayValue: { + fontSize, + alignment: { + horizontal: 'center', + vertical: 'middle', + }, + }, + }, + axes: { + axisTitle: { + padding: { + outer: 10, + }, + }, + }, + chartMargins: + legendPosition === Position.Top || legendPosition === Position.Right + ? { + bottom: (theme.chartMargins?.bottom ?? 0) + 10, + } + : { + right: (theme.chartMargins?.right ?? 0) + 10, + }, + }; + + const headerValueFormatter: TickFormatter | undefined = xAxis.ticks?.formatter + ? (value) => fillEmptyValue(xAxis.ticks?.formatter?.(value)) ?? '' + : undefined; + const headerFormatter = + isTimeChart && xDomain && adjustedXDomain + ? renderEndzoneTooltip( + xDomain.minInterval, + 'min' in xDomain ? xDomain.min : undefined, + 'max' in xDomain ? xDomain.max : undefined, + headerValueFormatter, + !tooltip.detailedTooltip + ) + : headerValueFormatter && + (tooltip.detailedTooltip ? undefined : ({ value }: any) => headerValueFormatter(value)); + + const tooltipProps: TooltipProps = tooltip.detailedTooltip + ? { + ...tooltip, + customTooltip: tooltip.detailedTooltip(headerFormatter), + headerFormatter: undefined, + } + : { ...tooltip, headerFormatter }; + + return ( + + ); +}; diff --git a/src/plugins/vis_type_xy/public/components/xy_threshold_line.tsx b/src/plugins/vis_type_xy/public/components/xy_threshold_line.tsx new file mode 100644 index 0000000000000..46b0a009807a2 --- /dev/null +++ b/src/plugins/vis_type_xy/public/components/xy_threshold_line.tsx @@ -0,0 +1,58 @@ +/* + * 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 React, { FC } from 'react'; + +import { AnnotationDomainTypes, LineAnnotation } from '@elastic/charts'; + +import { ThresholdLineConfig } from '../types'; + +type XYThresholdLineProps = ThresholdLineConfig & { + groupId?: string; +}; + +export const XYThresholdLine: FC = ({ + show, + value: dataValue, + color, + width, + groupId, + dash, +}) => { + if (!show) { + return null; + } + + return ( + + ); +}; diff --git a/src/plugins/vis_type_xy/public/config/get_agg_id.ts b/src/plugins/vis_type_xy/public/config/get_agg_id.ts new file mode 100644 index 0000000000000..7923145d83013 --- /dev/null +++ b/src/plugins/vis_type_xy/public/config/get_agg_id.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +/** + * Get agg id from accessor + * + * For now this is determined by the esaggs column name. Could be cleaned up in the future. + */ +export const getAggId = (accessor: string) => (accessor ?? '').split('-').pop() ?? ''; diff --git a/src/plugins/vis_type_xy/public/config/get_aspects.ts b/src/plugins/vis_type_xy/public/config/get_aspects.ts new file mode 100644 index 0000000000000..73096de0a5d51 --- /dev/null +++ b/src/plugins/vis_type_xy/public/config/get_aspects.ts @@ -0,0 +1,95 @@ +/* + * 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 { compact } from 'lodash'; + +import { i18n } from '@kbn/i18n'; + +import { DatatableColumn } from '../../../expressions/public'; + +import { Aspect, Dimension, Aspects, Dimensions } from '../types'; +import { getFormatService } from '../services'; +import { getAggId } from './get_agg_id'; + +export function getEmptyAspect(): Aspect { + return { + accessor: null, + aggId: null, + aggType: null, + title: i18n.translate('visTypeXy.aggResponse.allDocsTitle', { + defaultMessage: 'All docs', + }), + params: { + defaultValue: '_all', + }, + }; +} +export function getAspects(columns: DatatableColumn[], { x, y, z, series }: Dimensions): Aspects { + const seriesDimensions = Array.isArray(series) || series === undefined ? series : [series]; + + return { + x: getAspectsFromDimension(columns, x) ?? getEmptyAspect(), + y: getAspectsFromDimension(columns, y) ?? [], + z: z && z?.length > 0 ? getAspectsFromDimension(columns, z[0]) : undefined, + series: getAspectsFromDimension(columns, seriesDimensions), + }; +} + +function getAspectsFromDimension( + columns: DatatableColumn[], + dimension?: Dimension | null +): Aspect | undefined; +function getAspectsFromDimension( + columns: DatatableColumn[], + dimensions?: Dimension[] | null +): Aspect[] | undefined; +function getAspectsFromDimension( + columns: DatatableColumn[], + dimensions?: Dimension | Dimension[] | null +): Aspect[] | Aspect | undefined { + if (!dimensions) { + return; + } + + if (Array.isArray(dimensions)) { + return compact( + dimensions.map((d) => { + const column = d && columns[d.accessor]; + return column && getAspect(column, d); + }) + ); + } + + const column = columns[dimensions.accessor]; + return column && getAspect(column, dimensions); +} + +const getAspect = ( + { id: accessor, name: title }: DatatableColumn, + { accessor: column, format, params, aggType }: Dimension +): Aspect => ({ + accessor, + column, + title, + format, + aggType, + aggId: getAggId(accessor), + formatter: (value: any) => getFormatService().deserialize(format).convert(value), + params, +}); diff --git a/src/plugins/vis_type_xy/public/config/get_axis.ts b/src/plugins/vis_type_xy/public/config/get_axis.ts new file mode 100644 index 0000000000000..43a9e25e0e287 --- /dev/null +++ b/src/plugins/vis_type_xy/public/config/get_axis.ts @@ -0,0 +1,198 @@ +/* + * 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 { identity, isNil } from 'lodash'; + +import { AxisSpec, TickFormatter, YDomainRange, ScaleType as ECScaleType } from '@elastic/charts'; + +import { LabelRotation } from '../../../charts/public'; +import { BUCKET_TYPES } from '../../../data/public'; + +import { + Aspect, + CategoryAxis, + Grid, + AxisConfig, + TickOptions, + ScaleConfig, + Scale, + ScaleType, + AxisType, + XScaleType, + YScaleType, + SeriesParam, +} from '../types'; +import { fillEmptyValue } from '../utils/get_series_name_fn'; + +export function getAxis( + { type, title: axisTitle, labels, scale: axisScale, ...axis }: CategoryAxis, + { categoryLines, valueAxis }: Grid, + { params, format, formatter, title: fallbackTitle = '', aggType }: Aspect, + seriesParams: SeriesParam[], + isDateHistogram = false +): AxisConfig { + const isCategoryAxis = type === AxisType.Category; + // Hide unassigned axis, not supported in elastic charts + // TODO: refactor when disallowing unassigned axes + // https://github.com/elastic/kibana/issues/82752 + const show = + (isCategoryAxis || seriesParams.some(({ valueAxis: id }) => id === axis.id)) && axis.show; + const groupId = axis.id; + + const grid = isCategoryAxis + ? { + show: categoryLines, + } + : { + show: valueAxis === axis.id, + }; + // Date range formatter applied on xAccessor + const tickFormatter = + aggType === BUCKET_TYPES.DATE_RANGE || aggType === BUCKET_TYPES.RANGE ? identity : formatter; + const ticks: TickOptions = { + formatter: tickFormatter, + labelFormatter: getLabelFormatter(labels.truncate, tickFormatter), + show: labels.show, + rotation: labels.rotate, + showOverlappingLabels: !labels.filter, + showDuplicates: !labels.filter, + }; + const scale = getScale(axisScale, params, format, isCategoryAxis); + const title = axisTitle.text || fallbackTitle; + const fallbackRotation = + isCategoryAxis && isDateHistogram ? LabelRotation.Horizontal : LabelRotation.Vertical; + + return { + ...axis, + show, + groupId, + title, + ticks, + grid, + scale, + style: getAxisStyle(ticks, title, fallbackRotation), + domain: getAxisDomain(scale, isCategoryAxis), + integersOnly: aggType === 'count', + }; +} + +function getLabelFormatter( + truncate?: number | null, + formatter?: TickFormatter +): TickFormatter | undefined { + if (truncate === null || truncate === undefined) { + return formatter; + } + + return (value: any) => { + const formattedStringValue = `${formatter ? formatter(value) : value}`; + const finalValue = fillEmptyValue(formattedStringValue); + + if (finalValue.length > truncate) { + return `${finalValue.slice(0, truncate)}...`; + } + + return finalValue; + }; +} + +function getScaleType( + scale?: Scale, + isNumber?: boolean, + isTime = false, + isHistogram = false +): ECScaleType | undefined { + if (isTime) return ECScaleType.Time; + if (isHistogram) return ECScaleType.Linear; + + if (!isNumber) { + return ECScaleType.Ordinal; + } + + const type = scale?.type; + if (type === ScaleType.SquareRoot) { + return ECScaleType.Sqrt; + } + + return type; +} + +function getScale( + scale: Scale, + params: Aspect['params'], + format: Aspect['format'], + isCategoryAxis: boolean +): ScaleConfig { + const type = (isCategoryAxis + ? getScaleType( + scale, + format?.id === 'number' || (format?.params?.id === 'number' && format?.id !== 'range'), + 'date' in params, + 'interval' in params + ) + : getScaleType(scale, true)) as S; + + return { + ...scale, + type, + }; +} + +function getAxisStyle( + ticks?: TickOptions, + title?: string, + rotationFallback: LabelRotation = LabelRotation.Vertical +): AxisSpec['style'] { + return { + axisTitle: { + visible: (title ?? '').trim().length > 0, + }, + tickLabel: { + visible: ticks?.show, + rotation: -(ticks?.rotation ?? rotationFallback), + }, + }; +} + +function getAxisDomain( + scale: ScaleConfig, + isCategoryAxis: boolean +): YDomainRange | undefined { + if (isCategoryAxis || !scale) { + return; + } + + const { min, max, defaultYExtents, boundsMargin } = scale; + const fit = defaultYExtents; + const padding = boundsMargin; + + if (!isNil(min) && !isNil(max)) { + return { fit, padding, min, max }; + } + + if (!isNil(min)) { + return { fit, padding, min }; + } + + if (!isNil(max)) { + return { fit, padding, max }; + } + + return { fit, padding }; +} diff --git a/src/plugins/vis_type_xy/public/config/get_config.ts b/src/plugins/vis_type_xy/public/config/get_config.ts new file mode 100644 index 0000000000000..f0c5740b1307b --- /dev/null +++ b/src/plugins/vis_type_xy/public/config/get_config.ts @@ -0,0 +1,134 @@ +/* + * 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 { ScaleContinuousType } from '@elastic/charts/dist/scales'; + +import { Datatable } from '../../../expressions/public'; +import { BUCKET_TYPES } from '../../../data/public'; + +import { + Aspect, + AxisConfig, + DateHistogramParams, + SeriesParam, + VisConfig, + VisParams, + XScaleType, + YScaleType, +} from '../types'; +import { getThresholdLine } from './get_threshold_line'; +import { getRotation } from './get_rotation'; +import { getTooltip } from './get_tooltip'; +import { getLegend } from './get_legend'; +import { getAxis } from './get_axis'; +import { getAspects } from './get_aspects'; +import { ChartType } from '../index'; + +export function getConfig(table: Datatable, params: VisParams): VisConfig { + const { + thresholdLine, + orderBucketsBySum, + addTimeMarker, + radiusRatio, + labels, + fittingFunction, + detailedTooltip, + isVislibVis, + } = params; + const aspects = getAspects(table.columns, params.dimensions); + const xAxis = getAxis( + params.categoryAxes[0], + params.grid, + aspects.x, + params.seriesParams, + params.dimensions.x?.aggType === BUCKET_TYPES.DATE_HISTOGRAM + ); + const tooltip = getTooltip(aspects, params); + const yAxes = params.valueAxes.map((a) => + // uses first y aspect in array for formatting axis + getAxis(a, params.grid, aspects.y[0], params.seriesParams) + ); + const enableHistogramMode = + (params.dimensions.x?.aggType === BUCKET_TYPES.DATE_HISTOGRAM || + params.dimensions.x?.aggType === BUCKET_TYPES.HISTOGRAM) && + shouldEnableHistogramMode(params.seriesParams, aspects.y, yAxes); + const isTimeChart = (aspects.x.params as DateHistogramParams).date ?? false; + + return { + // NOTE: downscale ratio to match current vislib implementation + markSizeRatio: radiusRatio * 0.6, + fittingFunction, + detailedTooltip, + orderBucketsBySum, + isTimeChart, + isVislibVis, + showCurrentTime: addTimeMarker && isTimeChart, + showValueLabel: labels.show ?? false, + enableHistogramMode, + tooltip, + aspects, + xAxis, + yAxes, + legend: getLegend(params), + rotation: getRotation(params.categoryAxes[0]), + thresholdLine: getThresholdLine(thresholdLine, yAxes, params.seriesParams), + }; +} + +/** + * disables histogram mode for any config that has non-stacked clustered bars + * + * @param seriesParams + * @param yAspects + * @param yAxes + */ +const shouldEnableHistogramMode = ( + seriesParams: SeriesParam[], + yAspects: Aspect[], + yAxes: Array> +): boolean => { + const bars = seriesParams.filter(({ type, data: { id: paramId } }) => { + return ( + type === ChartType.Histogram && yAspects.find(({ aggId }) => aggId === paramId) !== undefined + ); + }); + + if (bars.length === 1) { + return true; + } + + const groupIds = [ + ...bars.reduce>((acc, { valueAxis: groupId, mode }) => { + acc.add(groupId); + return acc; + }, new Set()), + ]; + + if (groupIds.length > 1) { + return false; + } + + const test = bars.every(({ valueAxis: groupId, mode }) => { + const yAxisScale = yAxes.find(({ groupId: axisGroupId }) => axisGroupId === groupId)?.scale; + + return mode === 'stacked' || yAxisScale?.mode === 'percentage'; + }); + + return test; +}; diff --git a/src/plugins/vis_type_xy/public/config/get_legend.ts b/src/plugins/vis_type_xy/public/config/get_legend.ts new file mode 100644 index 0000000000000..a376ec3c4fb96 --- /dev/null +++ b/src/plugins/vis_type_xy/public/config/get_legend.ts @@ -0,0 +1,27 @@ +/* + * 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 { LegendOptions, VisParams } from '../types'; + +export function getLegend({ addLegend, legendPosition }: VisParams): LegendOptions { + return { + show: addLegend, + position: legendPosition, + }; +} diff --git a/test/functional/apps/getting_started/index.js b/src/plugins/vis_type_xy/public/config/get_rotation.ts similarity index 66% rename from test/functional/apps/getting_started/index.js rename to src/plugins/vis_type_xy/public/config/get_rotation.ts index 399d3fe87484b..3c1e9a8f9130a 100644 --- a/test/functional/apps/getting_started/index.js +++ b/src/plugins/vis_type_xy/public/config/get_rotation.ts @@ -17,16 +17,14 @@ * under the License. */ -export default function ({ getService, loadTestFile }) { - const browser = getService('browser'); +import { Rotation } from '@elastic/charts'; - describe('Getting Started ', function () { - this.tags(['ciGroup6']); +import { CategoryAxis } from '../types'; - before(async function () { - await browser.setWindowSize(1200, 800); - }); - // https://www.elastic.co/guide/en/kibana/current/tutorial-load-dataset.html - loadTestFile(require.resolve('./_shakespeare')); - }); +export function getRotation({ position }: CategoryAxis): Rotation { + if (position === 'left' || position === 'right') { + return 90; + } + + return 0; } diff --git a/src/plugins/vis_type_xy/public/config/get_threshold_line.ts b/src/plugins/vis_type_xy/public/config/get_threshold_line.ts new file mode 100644 index 0000000000000..861769f829074 --- /dev/null +++ b/src/plugins/vis_type_xy/public/config/get_threshold_line.ts @@ -0,0 +1,54 @@ +/* + * 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 { + ThresholdLineConfig, + ThresholdLine, + ThresholdLineStyle, + AxisConfig, + SeriesParam, + YScaleType, +} from '../types'; + +export function getThresholdLine( + { style, ...rest }: ThresholdLine, + yAxes: Array>, + seriesParams: SeriesParam[] +): ThresholdLineConfig { + const groupId = yAxes.find(({ id }) => seriesParams.some(({ valueAxis }) => id === valueAxis)) + ?.groupId; + + return { + ...rest, + dash: getDash(style), + groupId, + }; +} + +function getDash(style: ThresholdLineStyle): number[] | undefined { + switch (style) { + case ThresholdLineStyle.Dashed: + return [10, 5]; + case ThresholdLineStyle.DotDashed: + return [20, 5, 5, 5]; + case ThresholdLineStyle.Full: + default: + return; + } +} diff --git a/src/plugins/vis_type_xy/public/config/get_tooltip.ts b/src/plugins/vis_type_xy/public/config/get_tooltip.ts new file mode 100644 index 0000000000000..87146eeca9155 --- /dev/null +++ b/src/plugins/vis_type_xy/public/config/get_tooltip.ts @@ -0,0 +1,33 @@ +/* + * 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 { TooltipType } from '@elastic/charts'; + +import { Aspects, VisParams, TooltipConfig } from '../types'; +import { getDetailedTooltip } from '../components/detailed_tooltip'; + +export function getTooltip( + aspects: Aspects, + { addTooltip, detailedTooltip }: VisParams +): TooltipConfig { + return { + type: addTooltip ? TooltipType.VerticalCursor : TooltipType.None, + detailedTooltip: detailedTooltip ? getDetailedTooltip(aspects) : undefined, + }; +} diff --git a/src/plugins/vis_type_xy/public/config/index.ts b/src/plugins/vis_type_xy/public/config/index.ts new file mode 100644 index 0000000000000..2b0e32f726a6c --- /dev/null +++ b/src/plugins/vis_type_xy/public/config/index.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +export { getConfig } from './get_config'; +export { getAggId } from './get_agg_id'; diff --git a/src/plugins/vis_type_xy/public/editor/collections.ts b/src/plugins/vis_type_xy/public/editor/collections.ts new file mode 100644 index 0000000000000..0149bc476f5c5 --- /dev/null +++ b/src/plugins/vis_type_xy/public/editor/collections.ts @@ -0,0 +1,201 @@ +/* + * 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 { Fit } from '@elastic/charts'; + +import { AxisMode, ChartMode, InterpolationMode, ThresholdLineStyle } from '../types'; +import { ChartType } from '../../common'; +import { LabelRotation } from '../../../charts/public'; +import { getScaleTypes } from './scale_types'; +import { getPositions } from './positions'; + +export { getScaleTypes, getPositions }; + +export const getChartTypes = () => [ + { + text: i18n.translate('visTypeXy.chartTypes.lineText', { + defaultMessage: 'Line', + }), + value: ChartType.Line, + }, + { + text: i18n.translate('visTypeXy.chartTypes.areaText', { + defaultMessage: 'Area', + }), + value: ChartType.Area, + }, + { + text: i18n.translate('visTypeXy.chartTypes.barText', { + defaultMessage: 'Bar', + }), + value: ChartType.Histogram, + }, +]; + +export const getChartModes = () => [ + { + text: i18n.translate('visTypeXy.chartModes.normalText', { + defaultMessage: 'Normal', + }), + value: ChartMode.Normal, + }, + { + text: i18n.translate('visTypeXy.chartModes.stackedText', { + defaultMessage: 'Stacked', + }), + value: ChartMode.Stacked, + }, +]; + +export const getInterpolationModes = () => [ + { + text: i18n.translate('visTypeXy.interpolationModes.straightText', { + defaultMessage: 'Straight', + }), + value: InterpolationMode.Linear, + }, + { + text: i18n.translate('visTypeXy.interpolationModes.smoothedText', { + defaultMessage: 'Smoothed', + }), + value: InterpolationMode.Cardinal, + }, + { + text: i18n.translate('visTypeXy.interpolationModes.steppedText', { + defaultMessage: 'Stepped', + }), + value: InterpolationMode.StepAfter, + }, +]; + +export const getAxisModes = () => [ + { + text: i18n.translate('visTypeXy.axisModes.normalText', { + defaultMessage: 'Normal', + }), + value: AxisMode.Normal, + }, + { + text: i18n.translate('visTypeXy.axisModes.percentageText', { + defaultMessage: 'Percentage', + }), + value: AxisMode.Percentage, + }, + { + text: i18n.translate('visTypeXy.axisModes.wiggleText', { + defaultMessage: 'Wiggle', + }), + value: AxisMode.Wiggle, + }, + { + text: i18n.translate('visTypeXy.axisModes.silhouetteText', { + defaultMessage: 'Silhouette', + }), + value: AxisMode.Silhouette, + }, +]; + +export const getThresholdLineStyles = () => [ + { + value: ThresholdLineStyle.Full, + text: i18n.translate('visTypeXy.thresholdLine.style.fullText', { + defaultMessage: 'Full', + }), + }, + { + value: ThresholdLineStyle.Dashed, + text: i18n.translate('visTypeXy.thresholdLine.style.dashedText', { + defaultMessage: 'Dashed', + }), + }, + { + value: ThresholdLineStyle.DotDashed, + text: i18n.translate('visTypeXy.thresholdLine.style.dotdashedText', { + defaultMessage: 'Dot-dashed', + }), + }, +]; + +export const getRotateOptions = () => [ + { + text: i18n.translate('visTypeXy.categoryAxis.rotate.horizontalText', { + defaultMessage: 'Horizontal', + }), + value: LabelRotation.Horizontal, + }, + { + text: i18n.translate('visTypeXy.categoryAxis.rotate.verticalText', { + defaultMessage: 'Vertical', + }), + value: LabelRotation.Vertical, + }, + { + text: i18n.translate('visTypeXy.categoryAxis.rotate.angledText', { + defaultMessage: 'Angled', + }), + value: LabelRotation.Angled, + }, +]; + +export const getFittingFunctions = () => + [ + { + value: Fit.None, + text: i18n.translate('visTypeXy.fittingFunctionsTitle.none', { + defaultMessage: 'Hide (Do not fill gaps)', + }), + }, + { + value: Fit.Zero, + text: i18n.translate('visTypeXy.fittingFunctionsTitle.zero', { + defaultMessage: 'Zero (Fill gaps with zeros)', + }), + }, + { + value: Fit.Linear, + text: i18n.translate('visTypeXy.fittingFunctionsTitle.linear', { + defaultMessage: 'Linear (Fill gaps with a line)', + }), + }, + { + value: Fit.Carry, + text: i18n.translate('visTypeXy.fittingFunctionsTitle.carry', { + defaultMessage: 'Last (Fill gaps with the last value)', + }), + }, + { + value: Fit.Lookahead, + text: i18n.translate('visTypeXy.fittingFunctionsTitle.lookahead', { + defaultMessage: 'Next (Fill gaps with the next value)', + }), + }, + ] as const; + +export const getConfigCollections = () => ({ + legendPositions: getPositions(), + positions: getPositions(), + chartTypes: getChartTypes(), + axisModes: getAxisModes(), + scaleTypes: getScaleTypes(), + chartModes: getChartModes(), + interpolationModes: getInterpolationModes(), + thresholdLineStyles: getThresholdLineStyles(), + fittingFunctions: getFittingFunctions(), +}); diff --git a/src/plugins/vis_type_vislib/public/utils/common_config.tsx b/src/plugins/vis_type_xy/public/editor/common_config.tsx similarity index 59% rename from src/plugins/vis_type_vislib/public/utils/common_config.tsx rename to src/plugins/vis_type_xy/public/editor/common_config.tsx index de867dc72bba7..31d1a9d539621 100644 --- a/src/plugins/vis_type_vislib/public/utils/common_config.tsx +++ b/src/plugins/vis_type_xy/public/editor/common_config.tsx @@ -20,36 +20,37 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; -import { PointSeriesOptions, MetricsAxisOptions } from '../components/options'; -import { ValidationWrapper } from '../components/common'; -import { BasicVislibParams } from '../types'; +import { VisOptionsProps } from '../../../vis_default_editor/public'; -function getAreaOptionTabs() { +import { VisParams } from '../types'; +import { MetricsAxisOptions, PointSeriesOptions } from './components/options'; +import { ValidationWrapper } from './components/common'; + +export function getOptionTabs(showElasticChartsOptions = false) { return [ { name: 'advanced', - title: i18n.translate('visTypeVislib.area.tabs.metricsAxesTitle', { + title: i18n.translate('visTypeXy.area.tabs.metricsAxesTitle', { defaultMessage: 'Metrics & axes', }), - editor: (props: VisOptionsProps) => ( + editor: (props: VisOptionsProps) => ( ), }, { name: 'options', - title: i18n.translate('visTypeVislib.area.tabs.panelSettingsTitle', { + title: i18n.translate('visTypeXy.area.tabs.panelSettingsTitle', { defaultMessage: 'Panel settings', }), - editor: (props: VisOptionsProps) => ( - + editor: (props: VisOptionsProps) => ( + ), }, ]; } - -const countLabel = i18n.translate('visTypeVislib.area.countText', { - defaultMessage: 'Count', -}); - -export { getAreaOptionTabs, countLabel }; diff --git a/src/plugins/vis_type_vislib/public/components/common/index.ts b/src/plugins/vis_type_xy/public/editor/components/common/index.ts similarity index 100% rename from src/plugins/vis_type_vislib/public/components/common/index.ts rename to src/plugins/vis_type_xy/public/editor/components/common/index.ts diff --git a/src/plugins/vis_type_vislib/public/components/common/truncate_labels.tsx b/src/plugins/vis_type_xy/public/editor/components/common/truncate_labels.tsx similarity index 96% rename from src/plugins/vis_type_vislib/public/components/common/truncate_labels.tsx rename to src/plugins/vis_type_xy/public/editor/components/common/truncate_labels.tsx index 6f55f6f070d00..ae1f34376bbf8 100644 --- a/src/plugins/vis_type_vislib/public/components/common/truncate_labels.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/common/truncate_labels.tsx @@ -33,7 +33,7 @@ function TruncateLabelsOption({ disabled, value = null, setValue }: TruncateLabe return ( extends VisOptionsProps { +export interface ValidationVisOptionsProps extends VisOptionsProps { setMultipleValidity(paramName: string, isValid: boolean): void; + extraProps?: E; } -interface ValidationWrapperProps extends VisOptionsProps { - component: React.ComponentType>; +interface ValidationWrapperProps extends VisOptionsProps { + component: React.ComponentType>; + extraProps?: E; } interface Item { isValid: boolean; } -function ValidationWrapper({ +function ValidationWrapper({ component: Component, ...rest -}: ValidationWrapperProps) { +}: ValidationWrapperProps) { const [panelState, setPanelState] = useState({} as { [key: string]: Item }); const isPanelValid = Object.values(panelState).every((item) => item.isValid); const { setValidity } = rest; diff --git a/src/plugins/vis_type_vislib/public/components/index.ts b/src/plugins/vis_type_xy/public/editor/components/index.ts similarity index 100% rename from src/plugins/vis_type_vislib/public/components/index.ts rename to src/plugins/vis_type_xy/public/editor/components/index.ts diff --git a/src/plugins/vis_type_xy/public/editor/components/options/index.tsx b/src/plugins/vis_type_xy/public/editor/components/options/index.tsx new file mode 100644 index 0000000000000..3eba7fa3ad818 --- /dev/null +++ b/src/plugins/vis_type_xy/public/editor/components/options/index.tsx @@ -0,0 +1,39 @@ +/* + * 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 React, { lazy } from 'react'; + +import { VisParams } from '../../../types'; +import { ValidationVisOptionsProps } from '../common'; + +const PointSeriesOptionsLazy = lazy(() => import('./point_series')); +const MetricsAxisOptionsLazy = lazy(() => import('./metrics_axes')); + +export const PointSeriesOptions = ( + props: ValidationVisOptionsProps< + VisParams, + { + showElasticChartsOptions: boolean; + } + > +) => ; + +export const MetricsAxisOptions = (props: ValidationVisOptionsProps) => ( + +); diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap similarity index 94% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap index 2b7c03084ec65..bc66c1940ac72 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap @@ -10,7 +10,7 @@ exports[`CategoryAxisPanel component should init with the default set of props 1

diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap similarity index 100% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/custom_extents_options.test.tsx.snap b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/custom_extents_options.test.tsx.snap similarity index 100% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/custom_extents_options.test.tsx.snap rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/custom_extents_options.test.tsx.snap diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/index.test.tsx.snap similarity index 99% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/index.test.tsx.snap index 09e0753d592e5..c5ce09d4d78af 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/index.test.tsx.snap @@ -74,7 +74,6 @@ exports[`MetricsAxisOptions component should init with the default set of props /> diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap similarity index 100% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap similarity index 98% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap index ed7ae45eed3a5..594511010b745 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap @@ -18,7 +18,7 @@ exports[`ValueAxesPanel component should init with the default set of props 1`]

@@ -120,7 +120,6 @@ exports[`ValueAxesPanel component should init with the default set of props 1`] } } index={0} - isCategoryAxisHorizontal={false} onValueAxisPositionChanged={[MockFunction]} setMultipleValidity={[MockFunction]} setParamByIndex={[MockFunction]} @@ -303,7 +302,6 @@ exports[`ValueAxesPanel component should init with the default set of props 1`] } } index={1} - isCategoryAxisHorizontal={false} onValueAxisPositionChanged={[MockFunction]} setMultipleValidity={[MockFunction]} setParamByIndex={[MockFunction]} diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap similarity index 96% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap index c4142fb487b6a..3f3ee66a85924 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap @@ -7,22 +7,18 @@ exports[`ValueAxisOptions component should init with the default set of props 1` options={ Array [ Object { - "disabled": false, "text": "Top", "value": "top", }, Object { - "disabled": true, "text": "Left", "value": "left", }, Object { - "disabled": true, "text": "Right", "value": "right", }, Object { - "disabled": false, "text": "Bottom", "value": "bottom", }, diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap similarity index 100% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.test.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/category_axis_panel.test.tsx similarity index 93% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.test.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/category_axis_panel.test.tsx index 44ed0d5aeddab..3bf844618720b 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.test.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/category_axis_panel.test.tsx @@ -20,16 +20,16 @@ import React from 'react'; import { shallow } from 'enzyme'; import { CategoryAxisPanel, CategoryAxisPanelProps } from './category_axis_panel'; -import { Axis } from '../../../types'; -import { Positions } from '../../../utils/collections'; +import { CategoryAxis } from '../../../../types'; import { LabelOptions } from './label_options'; import { categoryAxis, vis } from './mocks'; +import { Position } from '@elastic/charts'; describe('CategoryAxisPanel component', () => { let setCategoryAxis: jest.Mock; let onPositionChanged: jest.Mock; let defaultProps: CategoryAxisPanelProps; - let axis: Axis; + let axis: CategoryAxis; beforeEach(() => { setCategoryAxis = jest.fn(); @@ -60,7 +60,7 @@ describe('CategoryAxisPanel component', () => { }); it('should call onPositionChanged when position is changed', () => { - const value = Positions.RIGHT; + const value = Position.Right; const comp = shallow(); comp.find({ paramName: 'position' }).prop('setValue')('position', value); diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/category_axis_panel.tsx similarity index 78% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/category_axis_panel.tsx index 468fb1f8c315a..a551163747526 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/category_axis_panel.tsx @@ -19,20 +19,21 @@ import React, { useCallback } from 'react'; -import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; +import { Position } from '@elastic/charts'; + +import { SelectOption, SwitchOption } from '../../../../../../charts/public'; +import { VisOptionsProps } from '../../../../../../vis_default_editor/public'; -import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; -import { Axis } from '../../../types'; -import { SelectOption, SwitchOption } from '../../../../../charts/public'; import { LabelOptions, SetAxisLabel } from './label_options'; -import { Positions } from '../../../utils/collections'; +import { CategoryAxis } from '../../../../types'; export interface CategoryAxisPanelProps { - axis: Axis; - onPositionChanged: (position: Positions) => void; - setCategoryAxis: (value: Axis) => void; + axis: CategoryAxis; + onPositionChanged: (position: Position) => void; + setCategoryAxis: (value: CategoryAxis) => void; vis: VisOptionsProps['vis']; } @@ -43,7 +44,7 @@ function CategoryAxisPanel({ setCategoryAxis, }: CategoryAxisPanelProps) { const setAxis = useCallback( - (paramName: T, value: Axis[T]) => { + (paramName: T, value: CategoryAxis[T]) => { const updatedAxis = { ...axis, [paramName]: value, @@ -54,7 +55,7 @@ function CategoryAxisPanel({ ); const setPosition = useCallback( - (paramName: 'position', value: Axis['position']) => { + (paramName: 'position', value: CategoryAxis['position']) => { setAxis(paramName, value); onPositionChanged(value); }, @@ -77,7 +78,7 @@ function CategoryAxisPanel({

@@ -85,7 +86,7 @@ function CategoryAxisPanel({ { let setParamByIndex: jest.Mock; @@ -53,14 +54,14 @@ describe('ChartOptions component', () => { }); it('should show LineOptions when type is line', () => { - chart.type = ChartTypes.LINE; + chart.type = ChartType.Line; const comp = shallow(); expect(comp.find(LineOptions).exists()).toBeTruthy(); }); it('should show line mode when type is area', () => { - chart.type = ChartTypes.AREA; + chart.type = ChartType.Area; const comp = shallow(); expect(comp.find({ paramName: 'interpolate' }).exists()).toBeTruthy(); @@ -78,8 +79,8 @@ describe('ChartOptions component', () => { it('should call setParamByIndex when mode is changed', () => { const comp = shallow(); const paramName = 'mode'; - comp.find({ paramName }).prop('setValue')(paramName, ChartModes.NORMAL); + comp.find({ paramName }).prop('setValue')(paramName, ChartMode.Normal); - expect(setParamByIndex).toBeCalledWith('seriesParams', 0, paramName, ChartModes.NORMAL); + expect(setParamByIndex).toBeCalledWith('seriesParams', 0, paramName, ChartMode.Normal); }); }); diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx similarity index 80% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx index 623a8d1f348e9..c379fa30b49b8 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx @@ -22,12 +22,13 @@ import React, { useMemo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { Vis } from '../../../../../visualizations/public'; -import { SeriesParam, ValueAxis } from '../../../types'; -import { ChartTypes } from '../../../utils/collections'; -import { SelectOption } from '../../../../../charts/public'; +import { Vis } from '../../../../../../visualizations/public'; +import { SelectOption } from '../../../../../../charts/public'; + +import { SeriesParam, ValueAxis } from '../../../../types'; import { LineOptions } from './line_options'; import { SetParamByIndex, ChangeValueAxis } from '.'; +import { ChartType } from '../../../../../common'; export type SetChart = (paramName: T, value: SeriesParam[T]) => void; @@ -69,7 +70,7 @@ function ChartOptions({ value: id, })), { - text: i18n.translate('visTypeVislib.controls.pointSeries.series.newAxisLabel', { + text: i18n.translate('visTypeXy.controls.pointSeries.series.newAxisLabel', { defaultMessage: 'New axis…', }), value: 'new', @@ -82,7 +83,7 @@ function ChartOptions({ <>
- {chart.type === ChartTypes.AREA && ( + {chart.type === ChartType.Area && ( <> )} - {chart.type === ChartTypes.LINE && ( - - )} + {chart.type === ChartType.Line && } ); } diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.test.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/custom_extents_options.test.tsx similarity index 99% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.test.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/custom_extents_options.test.tsx index 4798c67928f7f..82097271f8814 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.test.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/custom_extents_options.test.tsx @@ -19,6 +19,7 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; + import { CustomExtentsOptions, CustomExtentsOptionsProps } from './custom_extents_options'; import { YExtents } from './y_extents'; import { valueAxis } from './mocks'; diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/custom_extents_options.tsx similarity index 87% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/custom_extents_options.tsx index 634d6b3f0641c..86a0c56e46942 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/custom_extents_options.tsx @@ -18,10 +18,12 @@ */ import React, { useCallback, useEffect } from 'react'; + import { i18n } from '@kbn/i18n'; -import { ValueAxis } from '../../../types'; -import { NumberInputOption, SwitchOption } from '../../../../../charts/public'; +import { NumberInputOption, SwitchOption } from '../../../../../../charts/public'; + +import { ValueAxis } from '../../../../types'; import { YExtents } from './y_extents'; import { SetScale } from './value_axis_options'; @@ -39,7 +41,7 @@ function CustomExtentsOptions({ setValueAxisScale, }: CustomExtentsOptionsProps) { const invalidBoundsMarginMessage = i18n.translate( - 'visTypeVislib.controls.pointSeries.valueAxes.scaleToDataBounds.minNeededBoundsMargin', + 'visTypeXy.controls.pointSeries.valueAxes.scaleToDataBounds.minNeededBoundsMargin', { defaultMessage: 'Bounds margin must be greater than or equal to 0.' } ); @@ -84,12 +86,9 @@ function CustomExtentsOptions({ return ( <> ({ + SeriesPanel: () => 'SeriesPanel', +})); +jest.mock('./category_axis_panel', () => ({ + CategoryAxisPanel: () => 'CategoryAxisPanel', +})); +jest.mock('./value_axes_panel', () => ({ + ValueAxesPanel: () => 'ValueAxesPanel', +})); + +const SERIES_PARAMS = 'seriesParams'; +const VALUE_AXES = 'valueAxes'; + +const aggCount: IAggConfig = { + id: '1', + type: { name: 'count' }, + makeLabel: () => 'Count', +} as IAggConfig; + +const aggAverage: IAggConfig = { + id: '2', + type: { name: 'average' } as IAggType, + makeLabel: () => 'Average', +} as IAggConfig; + +const createAggs = (aggs: any[]) => ({ + aggs, + bySchemaName: () => aggs, +}); + +describe('MetricsAxisOptions component', () => { + let setValue: jest.Mock; + let defaultProps: ValidationVisOptionsProps; + let axis: ValueAxis; + let axisRight: ValueAxis; + let chart: SeriesParam; + + beforeEach(() => { + setValue = jest.fn(); + + axis = { + ...valueAxis, + name: 'LeftAxis-1', + position: Position.Left, + }; + axisRight = { + ...valueAxis, + id: 'ValueAxis-2', + name: 'RightAxis-1', + position: Position.Right, + }; + chart = { + ...seriesParam, + type: ChartType.Area, + }; + + defaultProps = { + aggs: createAggs([aggCount]), + isTabSelected: true, + vis: { + type: { + type: ChartType.Area, + schemas: { metrics: [{ name: 'metric' }] }, + }, + setState: jest.fn(), + serialize: jest.fn(), + }, + stateParams: { + valueAxes: [axis], + seriesParams: [chart], + categoryAxes: [categoryAxis], + grid: { + valueAxis: defaultValueAxisId, + }, + }, + setValue, + } as any; + }); + + it('should init with the default set of props', () => { + const component = shallow(); + + expect(component).toMatchSnapshot(); + }); + + describe('useEffect', () => { + it('should update series when new agg is added', () => { + const component = mount(); + component.setProps({ + aggs: createAggs([aggCount, aggAverage]), + }); + + const updatedSeries = [chart, { ...chart, data: { id: '2', label: aggAverage.makeLabel() } }]; + expect(setValue).toHaveBeenLastCalledWith(SERIES_PARAMS, updatedSeries); + }); + + it('should update series when new agg label is changed', () => { + const component = mount(); + const agg = { id: aggCount.id, makeLabel: () => 'New label' }; + component.setProps({ + aggs: createAggs([agg]), + }); + + const updatedSeries = [{ ...chart, data: { id: agg.id, label: agg.makeLabel() } }]; + expect(setValue).toHaveBeenCalledWith(SERIES_PARAMS, updatedSeries); + }); + }); + + describe('updateAxisTitle', () => { + it('should not update the value axis title if custom title was set', () => { + defaultProps.stateParams.valueAxes[0].title.text = 'Custom title'; + const component = mount(); + const newAgg = { + ...aggCount, + makeLabel: () => 'Custom label', + }; + component.setProps({ + aggs: createAggs([newAgg]), + }); + const updatedValues = [{ ...axis, title: { text: newAgg.makeLabel() } }]; + expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES, updatedValues); + }); + + it('should set the custom title to match the value axis label when only one agg exists for that axis', () => { + const component = mount(); + const agg = { + id: aggCount.id, + params: { customLabel: 'Custom label' }, + makeLabel: () => 'Custom label', + }; + component.setProps({ + aggs: createAggs([agg]), + }); + + const updatedSeriesParams = [{ ...chart, data: { ...chart.data, label: agg.makeLabel() } }]; + const updatedValues = [{ ...axis, title: { text: agg.makeLabel() } }]; + + expect(setValue).toHaveBeenCalledTimes(5); + expect(setValue).toHaveBeenNthCalledWith(3, SERIES_PARAMS, updatedSeriesParams); + expect(setValue).toHaveBeenNthCalledWith(5, SERIES_PARAMS, updatedSeriesParams); + expect(setValue).toHaveBeenNthCalledWith(4, VALUE_AXES, updatedValues); + }); + + it('should not set the custom title to match the value axis label when more than one agg exists for that axis', () => { + const component = mount(); + const agg = { id: aggCount.id, makeLabel: () => 'Custom label' }; + component.setProps({ + aggs: createAggs([agg, aggAverage]), + stateParams: { + ...defaultProps.stateParams, + seriesParams: [chart, chart], + }, + }); + + expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES); + }); + + it('should not overwrite the custom title with the value axis label if the custom title has been changed', () => { + defaultProps.stateParams.valueAxes[0].title.text = 'Custom title'; + const component = mount(); + const agg = { + id: aggCount.id, + params: { customLabel: 'Custom label' }, + makeLabel: () => 'Custom label', + }; + component.setProps({ + aggs: createAggs([agg]), + }); + + expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES); + }); + }); + + it('should add value axis', () => { + const component = shallow(); + component.find(ValueAxesPanel).prop('addValueAxis')(); + + expect(setValue).toHaveBeenCalledWith(VALUE_AXES, [axis, axisRight]); + }); + + describe('removeValueAxis', () => { + beforeEach(() => { + defaultProps.stateParams.valueAxes = [axis, axisRight]; + }); + + it('should remove value axis', () => { + const component = shallow(); + component.find(ValueAxesPanel).prop('removeValueAxis')(axis); + + expect(setValue).toHaveBeenCalledWith(VALUE_AXES, [axisRight]); + }); + + it('should update seriesParams "valueAxis" prop', () => { + const updatedSeriesParam = { ...chart, valueAxis: 'ValueAxis-2' }; + const component = shallow(); + component.find(ValueAxesPanel).prop('removeValueAxis')(axis); + + expect(setValue).toHaveBeenCalledWith(SERIES_PARAMS, [updatedSeriesParam]); + }); + + it('should reset grid "valueAxis" prop', () => { + const updatedGrid = { valueAxis: undefined }; + defaultProps.stateParams.seriesParams[0].valueAxis = 'ValueAxis-2'; + const component = shallow(); + component.find(ValueAxesPanel).prop('removeValueAxis')(axis); + + expect(setValue).toHaveBeenCalledWith('grid', updatedGrid); + }); + }); + + describe('onValueAxisPositionChanged', () => { + const getProps = ( + valuePosition1: Position = Position.Right, + valuePosition2: Position = Position.Left + ): ValidationVisOptionsProps => ({ + ...defaultProps, + stateParams: { + ...defaultProps.stateParams, + valueAxes: [ + { + ...valueAxis, + id: 'ValueAxis-1', + position: valuePosition1, + }, + { + ...valueAxis, + id: 'ValueAxis-2', + position: valuePosition2, + }, + { + ...valueAxis, + id: 'ValueAxis-3', + position: valuePosition2, + }, + ], + categoryAxes: [ + { + ...categoryAxis, + position: mapPosition(valuePosition1), + }, + ], + }, + }); + + it('should update all value axes if another value axis changes from horizontal to vertical', () => { + const component = mount(); + setValue.mockClear(); + const onValueAxisPositionChanged = component + .find(ValueAxesPanel) + .prop('onValueAxisPositionChanged'); + onValueAxisPositionChanged(0, Position.Bottom); + expect(setValue).nthCalledWith(1, 'categoryAxes', [ + expect.objectContaining({ + id: 'CategoryAxis-1', + position: Position.Right, + }), + ]); + expect(setValue).nthCalledWith(2, 'valueAxes', [ + expect.objectContaining({ + id: 'ValueAxis-1', + position: Position.Bottom, + }), + expect.objectContaining({ + id: 'ValueAxis-2', + position: Position.Top, + }), + expect.objectContaining({ + id: 'ValueAxis-3', + position: Position.Top, + }), + ]); + }); + + it('should update all value axes if another value axis changes from vertical to horizontal', () => { + const component = mount(); + setValue.mockClear(); + const onValueAxisPositionChanged = component + .find(ValueAxesPanel) + .prop('onValueAxisPositionChanged'); + onValueAxisPositionChanged(1, Position.Left); + expect(setValue).nthCalledWith(1, 'categoryAxes', [ + expect.objectContaining({ + id: 'CategoryAxis-1', + position: Position.Top, + }), + ]); + expect(setValue).nthCalledWith(2, 'valueAxes', [ + expect.objectContaining({ + id: 'ValueAxis-1', + position: Position.Right, + }), + expect.objectContaining({ + id: 'ValueAxis-2', + position: Position.Left, + }), + expect.objectContaining({ + id: 'ValueAxis-3', + position: Position.Left, + }), + ]); + }); + + it('should update only changed value axis if value axis stays horizontal', () => { + const component = mount(); + setValue.mockClear(); + const onValueAxisPositionChanged = component + .find(ValueAxesPanel) + .prop('onValueAxisPositionChanged'); + onValueAxisPositionChanged(0, Position.Left); + expect(setValue).nthCalledWith(1, 'valueAxes', [ + expect.objectContaining({ + id: 'ValueAxis-1', + position: Position.Left, + }), + expect.objectContaining({ + id: 'ValueAxis-2', + position: Position.Left, + }), + expect.objectContaining({ + id: 'ValueAxis-3', + position: Position.Left, + }), + ]); + }); + + it('should update only changed value axis if value axis stays vertical', () => { + const component = mount(); + setValue.mockClear(); + const onValueAxisPositionChanged = component + .find(ValueAxesPanel) + .prop('onValueAxisPositionChanged'); + onValueAxisPositionChanged(1, Position.Top); + expect(setValue).nthCalledWith(1, 'valueAxes', [ + expect.objectContaining({ + id: 'ValueAxis-1', + position: Position.Top, + }), + expect.objectContaining({ + id: 'ValueAxis-2', + position: Position.Top, + }), + expect.objectContaining({ + id: 'ValueAxis-3', + position: Position.Bottom, + }), + ]); + }); + }); + + describe('onCategoryAxisPositionChanged', () => { + const getProps = ( + position: Position = Position.Bottom + ): ValidationVisOptionsProps => ({ + ...defaultProps, + stateParams: { + ...defaultProps.stateParams, + valueAxes: [ + { + ...valueAxis, + id: 'ValueAxis-1', + position: mapPosition(position), + }, + { + ...valueAxis, + id: 'ValueAxis-2', + position: mapPositionOpposite(mapPosition(position)), + }, + { + ...valueAxis, + id: 'ValueAxis-3', + position: mapPosition(position), + }, + ], + categoryAxes: [ + { + ...categoryAxis, + position, + }, + ], + }, + }); + + it('should update all value axes if category axis changes from horizontal to vertical', () => { + const component = mount(); + setValue.mockClear(); + const onPositionChanged = component.find(CategoryAxisPanel).prop('onPositionChanged'); + onPositionChanged(Position.Left); + expect(setValue).nthCalledWith(1, 'valueAxes', [ + expect.objectContaining({ + id: 'ValueAxis-1', + position: Position.Bottom, + }), + expect.objectContaining({ + id: 'ValueAxis-2', + position: Position.Top, + }), + expect.objectContaining({ + id: 'ValueAxis-3', + position: Position.Bottom, + }), + ]); + }); + + it('should update all value axes if category axis changes from vertical to horizontal', () => { + const component = mount(); + setValue.mockClear(); + const onPositionChanged = component.find(CategoryAxisPanel).prop('onPositionChanged'); + onPositionChanged(Position.Top); + expect(setValue).nthCalledWith(1, 'valueAxes', [ + expect.objectContaining({ + id: 'ValueAxis-1', + position: Position.Left, + }), + expect.objectContaining({ + id: 'ValueAxis-2', + position: Position.Right, + }), + expect.objectContaining({ + id: 'ValueAxis-3', + position: Position.Left, + }), + ]); + }); + + it('should not update value axes if category axis stays horizontal', () => { + const component = mount(); + setValue.mockClear(); + const onPositionChanged = component.find(CategoryAxisPanel).prop('onPositionChanged'); + onPositionChanged(Position.Top); + expect(setValue).not.toBeCalled(); + }); + + it('should not update value axes if category axis stays vertical', () => { + const component = mount(); + setValue.mockClear(); + const onPositionChanged = component.find(CategoryAxisPanel).prop('onPositionChanged'); + onPositionChanged(Position.Right); + expect(setValue).not.toBeCalled(); + }); + }); +}); diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/index.tsx similarity index 83% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/index.tsx index 0862c47c35cff..9b4bf3127d45e 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/index.tsx @@ -19,10 +19,12 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { cloneDeep, get } from 'lodash'; + import { EuiSpacer } from '@elastic/eui'; -import { IAggConfig } from 'src/plugins/data/public'; -import { BasicVislibParams, ValueAxis, SeriesParam, Axis } from '../../../types'; +import { IAggConfig } from '../../../../../../data/public'; + +import { VisParams, ValueAxis, SeriesParam, CategoryAxis } from '../../../../types'; import { ValidationVisOptionsProps } from '../../common'; import { SeriesPanel } from './series_panel'; import { CategoryAxisPanel } from './category_axis_panel'; @@ -34,6 +36,7 @@ import { getUpdatedAxisName, mapPositionOpposite, mapPosition, + mapPositionOpposingOpposite, } from './utils'; export type SetParamByIndex =

( @@ -51,11 +54,9 @@ export type ChangeValueAxis = ( const VALUE_AXIS_PREFIX = 'ValueAxis-'; -function MetricsAxisOptions(props: ValidationVisOptionsProps) { +function MetricsAxisOptions(props: ValidationVisOptionsProps) { const { stateParams, setValue, aggs, vis, isTabSelected } = props; - const [isCategoryAxisHorizontal, setIsCategoryAxisHorizontal] = useState(true); - const setParamByIndex: SetParamByIndex = useCallback( (axesName, index, paramName, value) => { const items = stateParams[axesName]; @@ -72,7 +73,7 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) ); const setCategoryAxis = useCallback( - (value: Axis) => { + (value: CategoryAxis) => { const categoryAxes = [...stateParams.categoryAxes]; categoryAxes[0] = value; setValue('categoryAxes', categoryAxes); @@ -170,33 +171,58 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) ); const onValueAxisPositionChanged = useCallback( - (index: number, value: ValueAxis['position']) => { + (index: number, axisPosition: ValueAxis['position']) => { + const isHorizontalAxis = isAxisHorizontal(axisPosition); const valueAxes = [...stateParams.valueAxes]; - const name = getUpdatedAxisName(value, valueAxes); + const name = getUpdatedAxisName(axisPosition, valueAxes); + const [categoryAxes] = stateParams.categoryAxes; - valueAxes[index] = { - ...valueAxes[index], - name, - position: value, - }; - setValue('valueAxes', valueAxes); + if (isAxisHorizontal(categoryAxes.position) === isHorizontalAxis) { + const updatedCategoryAxes = { + ...categoryAxes, + position: mapPosition(categoryAxes.position), + }; + + setValue('categoryAxes', [updatedCategoryAxes]); + + const oldPosition = valueAxes[index].position; + const newValueAxes = valueAxes.map(({ position, ...axis }, i) => ({ + ...axis, + position: + i === index + ? axisPosition + : mapPositionOpposingOpposite(position, oldPosition, axisPosition), + })); + setValue('valueAxes', newValueAxes); + } else { + valueAxes[index] = { + ...valueAxes[index], + name, + position: axisPosition, + }; + setValue('valueAxes', valueAxes); + } }, - [stateParams.valueAxes, setValue] + [stateParams.valueAxes, stateParams.categoryAxes, setValue] ); const onCategoryAxisPositionChanged = useCallback( - (chartPosition: Axis['position']) => { - const isChartHorizontal = isAxisHorizontal(chartPosition); - setIsCategoryAxisHorizontal(isAxisHorizontal(chartPosition)); - - stateParams.valueAxes.forEach((axis, index) => { - if (isAxisHorizontal(axis.position) === isChartHorizontal) { - const position = mapPosition(axis.position); - onValueAxisPositionChanged(index, position); - } - }); + (axisPosition: CategoryAxis['position']) => { + const isHorizontalAxis = isAxisHorizontal(axisPosition); + + if ( + stateParams.valueAxes.some( + ({ position }) => isAxisHorizontal(position) === isHorizontalAxis + ) + ) { + const newValueAxes = stateParams.valueAxes.map(({ position, ...axis }) => ({ + ...axis, + position: mapPosition(position), + })); + setValue('valueAxes', newValueAxes); + } }, - [stateParams.valueAxes, onValueAxisPositionChanged] + [setValue, stateParams.valueAxes] ); const addValueAxis = useCallback(() => { @@ -305,7 +331,6 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) ( - paramName: T, - value: Axis['labels'][T] -) => void; +export type SetAxisLabel = (paramName: T, value: Labels[T]) => void; export interface LabelOptionsProps { - axisLabels: Axis['labels']; + axisLabels: Labels; axisFilterCheckboxName: string; setAxisLabel: SetAxisLabel; } function LabelOptions({ axisLabels, axisFilterCheckboxName, setAxisLabel }: LabelOptionsProps) { const setAxisLabelRotate = useCallback( - (paramName: 'rotate', value: Axis['labels']['rotate']) => { + (paramName: 'rotate', value: Labels['rotate']) => { setAxisLabel(paramName, Number(value)); }, [setAxisLabel] @@ -54,7 +51,7 @@ function LabelOptions({ axisLabels, axisFilterCheckboxName, setAxisLabel }: Labe

@@ -62,7 +59,7 @@ function LabelOptions({ axisLabels, axisFilterCheckboxName, setAxisLabel }: Labe

@@ -57,13 +58,10 @@ function SeriesPanel({ seriesParams, ...chartProps }: SeriesPanelProps) { initialIsOpen={index === 0} buttonContent={chart.data.label} buttonContentClassName="visEditorSidebar__aggGroupAccordionButtonContent eui-textTruncate" - aria-label={i18n.translate( - 'visTypeVislib.controls.pointSeries.seriesAccordionAriaLabel', - { - defaultMessage: 'Toggle {agg} options', - values: { agg: chart.data.label }, - } - )} + aria-label={i18n.translate('visTypeXy.controls.pointSeries.seriesAccordionAriaLabel', { + defaultMessage: 'Toggle {agg} options', + values: { agg: chart.data.label }, + })} > <> diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/utils.ts b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/utils.ts similarity index 53% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/utils.ts rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/utils.ts index 708e8cf15f029..58216ee8953cd 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/utils.ts +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/utils.ts @@ -16,13 +16,14 @@ * specific language governing permissions and limitations * under the License. */ - import { upperFirst } from 'lodash'; -import { BasicVislibParams, ValueAxis, SeriesParam } from '../../../types'; -import { ChartModes, ChartTypes, InterpolationModes, Positions } from '../../../utils/collections'; +import { Position } from '@elastic/charts'; + +import { VisParams, ValueAxis, SeriesParam, ChartMode, InterpolationMode } from '../../../../types'; +import { ChartType } from '../../../../../common'; -const makeSerie = ( +export const makeSerie = ( id: string, label: string, defaultValueAxis: ValueAxis['id'], @@ -31,11 +32,11 @@ const makeSerie = ( const data = { id, label }; const defaultSerie = { show: true, - mode: ChartModes.NORMAL, - type: ChartTypes.LINE, + mode: ChartMode.Normal, + type: ChartType.Line, drawLinesBetweenPoints: true, showCircles: true, - interpolate: InterpolationModes.LINEAR, + interpolate: InterpolationMode.Linear, lineWidth: 2, valueAxis: defaultValueAxis, data, @@ -43,12 +44,12 @@ const makeSerie = ( return lastSerie ? { ...lastSerie, data } : defaultSerie; }; -const isAxisHorizontal = (position: Positions) => - [Positions.TOP, Positions.BOTTOM].includes(position as any); +export const isAxisHorizontal = (position: Position) => + [Position.Top, Position.Bottom].includes(position as any); const RADIX = 10; -function countNextAxisNumber(axisName: string, axisProp: 'id' | 'name' = 'id') { +export function countNextAxisNumber(axisName: string, axisProp: 'id' | 'name' = 'id') { return (value: number, axis: ValueAxis) => { const nameLength = axisName.length; if (axis[axisProp].substr(0, nameLength) === axisName) { @@ -63,9 +64,9 @@ function countNextAxisNumber(axisName: string, axisProp: 'id' | 'name' = 'id') { const AXIS_PREFIX = 'Axis-'; -const getUpdatedAxisName = ( +export const getUpdatedAxisName = ( axisPosition: ValueAxis['position'], - valueAxes: BasicVislibParams['valueAxes'] + valueAxes: VisParams['valueAxes'] ) => { const axisName = upperFirst(axisPosition) + AXIS_PREFIX; const nextAxisNameNumber = valueAxes.reduce(countNextAxisNumber(axisName, 'name'), 1); @@ -73,39 +74,56 @@ const getUpdatedAxisName = ( return `${axisName}${nextAxisNameNumber}`; }; -function mapPositionOpposite(position: Positions) { +/** + * Maps axis position to opposite position + * @param position + */ +export function mapPositionOpposite(position: Position) { switch (position) { - case Positions.BOTTOM: - return Positions.TOP; - case Positions.TOP: - return Positions.BOTTOM; - case Positions.LEFT: - return Positions.RIGHT; - case Positions.RIGHT: - return Positions.LEFT; + case Position.Bottom: + return Position.Top; + case Position.Top: + return Position.Bottom; + case Position.Left: + return Position.Right; + case Position.Right: + return Position.Left; default: throw new Error('Invalid legend position.'); } } -function mapPosition(position: Positions) { - switch (position) { - case Positions.BOTTOM: - return Positions.LEFT; - case Positions.TOP: - return Positions.RIGHT; - case Positions.LEFT: - return Positions.BOTTOM; - case Positions.RIGHT: - return Positions.TOP; +/** + * Maps axis position to new position or opposite of new position based on old position + * @param position + * @param oldPosition + * @param newPosition + */ +export function mapPositionOpposingOpposite( + position: Position, + oldPosition: Position, + newPosition: Position +) { + if (position === oldPosition) { + return newPosition; } + + return mapPositionOpposite(newPosition); } -export { - makeSerie, - isAxisHorizontal, - countNextAxisNumber, - getUpdatedAxisName, - mapPositionOpposite, - mapPosition, -}; +/** + * Maps axis position to opposite rotation position + * @param position + */ +export function mapPosition(position: Position) { + switch (position) { + case Position.Bottom: + return Position.Left; + case Position.Top: + return Position.Right; + case Position.Left: + return Position.Bottom; + case Position.Right: + return Position.Top; + } +} diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.test.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/value_axes_panel.test.tsx similarity index 96% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.test.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/value_axes_panel.test.tsx index 070433ca03f6d..f3286029db32f 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.test.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/value_axes_panel.test.tsx @@ -19,10 +19,12 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { ValueAxesPanel, ValueAxesPanelProps } from './value_axes_panel'; -import { ValueAxis, SeriesParam } from '../../../types'; -import { Positions } from '../../../utils/collections'; + import { mountWithIntl } from '@kbn/test/jest'; +import { Position } from '@elastic/charts'; + +import { ValueAxis, SeriesParam } from '../../../../types'; +import { ValueAxesPanel, ValueAxesPanelProps } from './value_axes_panel'; import { valueAxis, seriesParam, vis } from './mocks'; describe('ValueAxesPanel component', () => { @@ -47,7 +49,7 @@ describe('ValueAxesPanel component', () => { axisRight = { ...valueAxis, id: 'ValueAxis-2', - position: Positions.RIGHT, + position: Position.Right, }; seriesParamCount = { ...seriesParam }; seriesParamAverage = { @@ -63,7 +65,6 @@ describe('ValueAxesPanel component', () => { seriesParams: [seriesParamCount, seriesParamAverage], valueAxes: [axisLeft, axisRight], vis, - isCategoryAxisHorizontal: false, setParamByIndex, onValueAxisPositionChanged, addValueAxis, diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/value_axes_panel.tsx similarity index 90% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/value_axes_panel.tsx index 5f7d33b7f1f47..397704663ff1f 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/value_axes_panel.tsx @@ -31,13 +31,13 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Vis } from '../../../../../visualizations/public'; -import { SeriesParam, ValueAxis } from '../../../types'; +import { Vis } from '../../../../../../visualizations/public'; + +import { SeriesParam, ValueAxis } from '../../../../types'; import { ValueAxisOptions } from './value_axis_options'; import { SetParamByIndex } from '.'; export interface ValueAxesPanelProps { - isCategoryAxisHorizontal: boolean; addValueAxis: () => ValueAxis; removeValueAxis: (axis: ValueAxis) => void; onValueAxisPositionChanged: (index: number, value: ValueAxis['position']) => void; @@ -64,7 +64,7 @@ function ValueAxesPanel(props: ValueAxesPanelProps) { const removeButtonTooltip = useMemo( () => - i18n.translate('visTypeVislib.controls.pointSeries.valueAxes.removeButtonTooltip', { + i18n.translate('visTypeXy.controls.pointSeries.valueAxes.removeButtonTooltip', { defaultMessage: 'Remove Y-axis', }), [] @@ -87,7 +87,7 @@ function ValueAxesPanel(props: ValueAxesPanelProps) { const addButtonTooltip = useMemo( () => - i18n.translate('visTypeVislib.controls.pointSeries.valueAxes.addButtonTooltip', { + i18n.translate('visTypeXy.controls.pointSeries.valueAxes.addButtonTooltip', { defaultMessage: 'Add Y-axis', }), [] @@ -115,7 +115,7 @@ function ValueAxesPanel(props: ValueAxesPanelProps) {

@@ -146,7 +146,7 @@ function ValueAxesPanel(props: ValueAxesPanelProps) { buttonClassName="eui-textTruncate" buttonContentClassName="visEditorSidebar__aggGroupAccordionButtonContent eui-textTruncate" aria-label={i18n.translate( - 'visTypeVislib.controls.pointSeries.valueAxes.toggleOptionsAriaLabel', + 'visTypeXy.controls.pointSeries.valueAxes.toggleOptionsAriaLabel', { defaultMessage: 'Toggle {axisName} options', values: { axisName: axis.name }, @@ -160,7 +160,6 @@ function ValueAxesPanel(props: ValueAxesPanelProps) { axis={axis} index={index} valueAxis={valueAxes[index]} - isCategoryAxisHorizontal={props.isCategoryAxisHorizontal} onValueAxisPositionChanged={props.onValueAxisPositionChanged} setParamByIndex={props.setParamByIndex} setMultipleValidity={props.setMultipleValidity} diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.test.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/value_axis_options.test.tsx similarity index 64% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.test.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/value_axis_options.test.tsx index 1977bdba6eadf..0b325198c3fe7 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.test.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/value_axis_options.test.tsx @@ -19,21 +19,18 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { ValueAxisOptions, ValueAxisOptionsParams } from './value_axis_options'; -import { ValueAxis } from '../../../types'; -import { TextInputOption } from '../../../../../charts/public'; + +import { Position } from '@elastic/charts'; + +import { TextInputOption } from '../../../../../../charts/public'; + +import { ValueAxis, ScaleType } from '../../../../types'; import { LabelOptions } from './label_options'; -import { ScaleTypes, Positions } from '../../../utils/collections'; +import { ValueAxisOptions, ValueAxisOptionsParams } from './value_axis_options'; import { valueAxis, vis } from './mocks'; const POSITION = 'position'; -interface PositionOption { - text: string; - value: Positions; - disabled: boolean; -} - describe('ValueAxisOptions component', () => { let setParamByIndex: jest.Mock; let onValueAxisPositionChanged: jest.Mock; @@ -52,7 +49,6 @@ describe('ValueAxisOptions component', () => { index: 0, valueAxis, vis, - isCategoryAxisHorizontal: false, setParamByIndex, onValueAxisPositionChanged, setMultipleValidity, @@ -73,50 +69,8 @@ describe('ValueAxisOptions component', () => { expect(comp.find(LabelOptions).exists()).toBeFalsy(); }); - it('should only allow left and right value axis position when category axis is horizontal', () => { - defaultProps.isCategoryAxisHorizontal = true; - const comp = shallow(); - - const options: PositionOption[] = comp.find({ paramName: POSITION }).prop('options'); - - expect(options.length).toBe(4); - options.forEach(({ value, disabled }) => { - switch (value) { - case Positions.LEFT: - case Positions.RIGHT: - expect(disabled).toBeFalsy(); - break; - case Positions.TOP: - case Positions.BOTTOM: - expect(disabled).toBeTruthy(); - break; - } - }); - }); - - it('should only allow top and bottom value axis position when category axis is vertical', () => { - defaultProps.isCategoryAxisHorizontal = false; - const comp = shallow(); - - const options: PositionOption[] = comp.find({ paramName: POSITION }).prop('options'); - - expect(options.length).toBe(4); - options.forEach(({ value, disabled }) => { - switch (value) { - case Positions.LEFT: - case Positions.RIGHT: - expect(disabled).toBeTruthy(); - break; - case Positions.TOP: - case Positions.BOTTOM: - expect(disabled).toBeFalsy(); - break; - } - }); - }); - it('should call onValueAxisPositionChanged when position is changed', () => { - const value = Positions.RIGHT; + const value = Position.Right; const comp = shallow(); comp.find({ paramName: POSITION }).prop('setValue')(POSITION, value); @@ -135,7 +89,7 @@ describe('ValueAxisOptions component', () => { }); it('should call setValueAxis when scale value is changed', () => { - const scaleValue = ScaleTypes.SQUARE_ROOT; + const scaleValue = ScaleType.SquareRoot; const comp = shallow(); comp.find({ paramName: 'type' }).prop('setValue')('type', scaleValue); diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/value_axis_options.tsx similarity index 76% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/value_axis_options.tsx index b42d038267d77..4ab792142e83a 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/value_axis_options.tsx @@ -17,17 +17,16 @@ * under the License. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiAccordion, EuiHorizontalRule } from '@elastic/eui'; -import { Vis } from '../../../../../visualizations/public'; -import { ValueAxis } from '../../../types'; -import { Positions } from '../../../utils/collections'; -import { SelectOption, SwitchOption, TextInputOption } from '../../../../../charts/public'; +import { Vis } from '../../../../../../visualizations/public'; +import { SelectOption, SwitchOption, TextInputOption } from '../../../../../../charts/public'; + +import { ValueAxis } from '../../../../types'; import { LabelOptions, SetAxisLabel } from './label_options'; import { CustomExtentsOptions } from './custom_extents_options'; -import { isAxisHorizontal } from './utils'; import { SetParamByIndex } from '.'; export type SetScale = ( @@ -38,7 +37,6 @@ export type SetScale = ( export interface ValueAxisOptionsParams { axis: ValueAxis; index: number; - isCategoryAxisHorizontal: boolean; onValueAxisPositionChanged: (index: number, value: ValueAxis['position']) => void; setParamByIndex: SetParamByIndex; valueAxis: ValueAxis; @@ -46,10 +44,9 @@ export interface ValueAxisOptionsParams { setMultipleValidity: (paramName: string, isValid: boolean) => void; } -function ValueAxisOptions({ +export function ValueAxisOptions({ axis, index, - isCategoryAxisHorizontal, valueAxis, vis, onValueAxisPositionChanged, @@ -105,34 +102,13 @@ function ValueAxisOptions({ [index, onValueAxisPositionChanged] ); - const isPositionDisabled = useCallback( - (position: Positions) => { - if (isCategoryAxisHorizontal) { - return isAxisHorizontal(position); - } - return [Positions.LEFT, Positions.RIGHT].includes(position as any); - }, - [isCategoryAxisHorizontal] - ); - - const positions = useMemo( - () => - vis.type.editorConfig.collections.positions.map( - (position: { text: string; value: Positions }) => ({ - ...position, - disabled: isPositionDisabled(position.value), - }) - ), - [vis.type.editorConfig.collections.positions, isPositionDisabled] - ); - return ( <> ); } - -export { ValueAxisOptions }; diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.test.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/y_extents.test.tsx similarity index 94% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.test.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/y_extents.test.tsx index 7bd5c9efdf29d..c2af7f2ad921b 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.test.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/y_extents.test.tsx @@ -19,9 +19,11 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; + +import { NumberInputOption } from '../../../../../../charts/public'; + +import { ScaleType } from '../../../../types'; import { YExtents, YExtentsProps } from './y_extents'; -import { ScaleTypes } from '../../../utils/collections'; -import { NumberInputOption } from '../../../../../charts/public'; describe('YExtents component', () => { let setMultipleValidity: jest.Mock; @@ -35,7 +37,7 @@ describe('YExtents component', () => { defaultProps = { scale: { - type: ScaleTypes.LINEAR, + type: ScaleType.Linear, }, setMultipleValidity, setScale, @@ -81,7 +83,7 @@ describe('YExtents component', () => { it('should call setMultipleValidity with false when min equals 0 and scale is log', () => { defaultProps.scale.min = 0; defaultProps.scale.max = 1; - defaultProps.scale.type = ScaleTypes.LOG; + defaultProps.scale.type = ScaleType.Log; mount(); expect(setMultipleValidity).toBeCalledWith(Y_EXTENTS, false); diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/y_extents.tsx similarity index 83% rename from src/plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/y_extents.tsx index 4e23ee5a41554..11d049d4864a7 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/y_extents.tsx @@ -21,15 +21,15 @@ import React, { useEffect, useCallback } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { Scale } from '../../../types'; -import { ScaleTypes } from '../../../utils/collections'; -import { NumberInputOption } from '../../../../../charts/public'; +import { NumberInputOption } from '../../../../../../charts/public'; + +import { Scale, ScaleType } from '../../../../types'; import { SetScale } from './value_axis_options'; -const rangeError = i18n.translate('visTypeVislib.controls.pointSeries.valueAxes.minErrorMessage', { +const rangeError = i18n.translate('visTypeXy.controls.pointSeries.valueAxes.minErrorMessage', { defaultMessage: 'Min should be less than Max.', }); -const minError = i18n.translate('visTypeVislib.controls.pointSeries.valueAxes.minNeededScaleText', { +const minError = i18n.translate('visTypeXy.controls.pointSeries.valueAxes.minNeededScaleText', { defaultMessage: 'Min must exceed 0 when a log scale is selected.', }); @@ -59,7 +59,7 @@ function YExtents({ scale, setScale, setMultipleValidity }: YExtentsProps) { errors.push(rangeError); } - if (type === ScaleTypes.LOG && (isNullOrUndefined(min) || min <= 0)) { + if (type === ScaleType.Log && (isNullOrUndefined(min) || min <= 0)) { errors.push(minError); } @@ -86,7 +86,7 @@ function YExtents({ scale, setScale, setMultipleValidity }: YExtentsProps) { ) { + const { stateParams, setValue, vis, aggs } = props; + + const hasLineChart = stateParams.seriesParams.some( + ({ type, data: { id: paramId } }) => + (type === ChartType.Line || type === ChartType.Area) && + aggs.aggs.find(({ id }) => id === paramId)?.enabled + ); + + return ( + <> + + + {hasLineChart && ( + + )} + + ); +} diff --git a/src/plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx b/src/plugins/vis_type_xy/public/editor/components/options/point_series/grid_panel.tsx similarity index 59% rename from src/plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/point_series/grid_panel.tsx index 0126dce37c9f2..c6ad52f7112c9 100644 --- a/src/plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/point_series/grid_panel.tsx @@ -16,25 +16,37 @@ * specific language governing permissions and limitations * under the License. */ + import React, { useMemo, useEffect, useCallback } from 'react'; -import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; + +import { SelectOption, SwitchOption } from '../../../../../../charts/public'; -import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; -import { SelectOption, SwitchOption } from '../../../../../charts/public'; -import { BasicVislibParams, ValueAxis } from '../../../types'; +import { VisParams, ValueAxis } from '../../../../types'; +import { ValidationVisOptionsProps } from '../../common'; -function GridPanel({ stateParams, setValue, hasHistogramAgg }: VisOptionsProps) { +type GridPanelOptions = ValidationVisOptionsProps< + VisParams, + { + showElasticChartsOptions: boolean; + } +>; + +function GridPanel({ stateParams, setValue, hasHistogramAgg, extraProps }: GridPanelOptions) { const setGrid = useCallback( - ( - paramName: T, - value: BasicVislibParams['grid'][T] - ) => setValue('grid', { ...stateParams.grid, [paramName]: value }), + (paramName: T, value: VisParams['grid'][T]) => + setValue('grid', { ...stateParams.grid, [paramName]: value }), [stateParams.grid, setValue] ); + const disableCategoryGridLines = useMemo( + () => !extraProps?.showElasticChartsOptions && hasHistogramAgg, + [extraProps?.showElasticChartsOptions, hasHistogramAgg] + ); + const options = useMemo( () => [ ...stateParams.valueAxes.map(({ id, name }: ValueAxis) => ({ @@ -42,7 +54,7 @@ function GridPanel({ stateParams, setValue, hasHistogramAgg }: VisOptionsProps { - if (hasHistogramAgg) { + if (disableCategoryGridLines) { setGrid('categoryLines', false); } - }, [hasHistogramAgg, setGrid]); + }, [disableCategoryGridLines, setGrid]); return (

@@ -71,19 +83,16 @@ function GridPanel({ stateParams, setValue, hasHistogramAgg }: VisOptionsProps
diff --git a/src/plugins/vis_type_vislib/public/components/options/point_series/index.ts b/src/plugins/vis_type_xy/public/editor/components/options/point_series/index.ts similarity index 100% rename from src/plugins/vis_type_vislib/public/components/options/point_series/index.ts rename to src/plugins/vis_type_xy/public/editor/components/options/point_series/index.ts diff --git a/src/plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx b/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.tsx similarity index 64% rename from src/plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.tsx index 18073d2218877..283fc28aed46e 100644 --- a/src/plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.tsx @@ -16,25 +16,41 @@ * specific language governing permissions and limitations * under the License. */ + import React, { useMemo } from 'react'; import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { uniq } from 'lodash'; -import { ValidationVisOptionsProps } from '../../common'; -import { BasicOptions, SwitchOption } from '../../../../../charts/public'; +import { BasicOptions, SwitchOption } from '../../../../../../charts/public'; +import { BUCKET_TYPES } from '../../../../../../data/public'; + +import { VisParams } from '../../../../types'; import { GridPanel } from './grid_panel'; import { ThresholdPanel } from './threshold_panel'; -import { BasicVislibParams } from '../../../types'; -import { ChartTypes } from '../../../utils/collections'; - -function PointSeriesOptions(props: ValidationVisOptionsProps) { - const { stateParams, setValue, vis } = props; +import { ChartType } from '../../../../../common'; +import { ValidationVisOptionsProps } from '../../common'; +import { ElasticChartsOptions } from './elastic_charts_options'; - const currentChartTypes = useMemo(() => uniq(stateParams.seriesParams.map(({ type }) => type)), [ - stateParams.seriesParams, - ]); +export function PointSeriesOptions( + props: ValidationVisOptionsProps< + VisParams, + { + // TODO: Remove when vis_type_vislib is removed + // https://github.com/elastic/kibana/issues/56143 + showElasticChartsOptions: boolean; + } + > +) { + const { stateParams, setValue, vis, aggs } = props; + const hasBarChart = useMemo( + () => + stateParams.seriesParams.some( + ({ type, data: { id: paramId } }) => + type === ChartType.Histogram && aggs.aggs.find(({ id }) => id === paramId)?.enabled + ), + [stateParams.seriesParams, aggs.aggs] + ); return ( <> @@ -42,7 +58,7 @@ function PointSeriesOptions(props: ValidationVisOptionsProps)

@@ -52,10 +68,11 @@ function PointSeriesOptions(props: ValidationVisOptionsProps) {vis.data.aggs!.aggs.some( - (agg) => agg.schema === 'segment' && agg.type.name === 'date_histogram' + (agg) => agg.schema === 'segment' && agg.type.name === BUCKET_TYPES.DATE_HISTOGRAM ) ? ( ) /> ) : ( ) /> )} - {currentChartTypes.includes(ChartTypes.HISTOGRAM) && ( + {hasBarChart && ( ) } /> )} + + {props.extraProps?.showElasticChartsOptions && } @@ -98,5 +118,3 @@ function PointSeriesOptions(props: ValidationVisOptionsProps) ); } - -export { PointSeriesOptions }; diff --git a/src/plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx b/src/plugins/vis_type_xy/public/editor/components/options/point_series/threshold_panel.tsx similarity index 77% rename from src/plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx rename to src/plugins/vis_type_xy/public/editor/components/options/point_series/threshold_panel.tsx index 964bb7d569b08..ec21a386a5679 100644 --- a/src/plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/point_series/threshold_panel.tsx @@ -17,39 +17,41 @@ * under the License. */ import React, { useCallback } from 'react'; -import { EuiPanel, EuiTitle, EuiColorPicker, EuiFormRow, EuiSpacer } from '@elastic/eui'; + import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiPanel, EuiTitle, EuiColorPicker, EuiFormRow, EuiSpacer } from '@elastic/eui'; -import { ValidationVisOptionsProps } from '../../common'; import { SelectOption, SwitchOption, RequiredNumberInputOption, -} from '../../../../../charts/public'; -import { BasicVislibParams } from '../../../types'; +} from '../../../../../../charts/public'; + +import { ValidationVisOptionsProps } from '../../common'; +import { VisParams } from '../../../../types'; function ThresholdPanel({ stateParams, setValue, setMultipleValidity, vis, -}: ValidationVisOptionsProps) { +}: ValidationVisOptionsProps) { const setThresholdLine = useCallback( - ( + ( paramName: T, - value: BasicVislibParams['thresholdLine'][T] + value: VisParams['thresholdLine'][T] ) => setValue('thresholdLine', { ...stateParams.thresholdLine, [paramName]: value }), [stateParams.thresholdLine, setValue] ); const setThresholdLineColor = useCallback( - (value: BasicVislibParams['thresholdLine']['color']) => setThresholdLine('color', value), + (value: VisParams['thresholdLine']['color']) => setThresholdLine('color', value), [setThresholdLine] ); const setThresholdLineValidity = useCallback( - (paramName: keyof BasicVislibParams['thresholdLine'], isValid: boolean) => + (paramName: keyof VisParams['thresholdLine'], isValid: boolean) => setMultipleValidity(`thresholdLine__${paramName}`, isValid), [setMultipleValidity] ); @@ -59,7 +61,7 @@ function ThresholdPanel({

@@ -67,7 +69,7 @@ function ThresholdPanel({ [ + { + text: i18n.translate('visTypeXy.legendPositions.topText', { + defaultMessage: 'Top', + }), + value: Position.Top, + }, + { + text: i18n.translate('visTypeXy.legendPositions.leftText', { + defaultMessage: 'Left', + }), + value: Position.Left, + }, + { + text: i18n.translate('visTypeXy.legendPositions.rightText', { + defaultMessage: 'Right', + }), + value: Position.Right, + }, + { + text: i18n.translate('visTypeXy.legendPositions.bottomText', { + defaultMessage: 'Bottom', + }), + value: Position.Bottom, + }, +]; diff --git a/src/plugins/vis_type_xy/public/editor/scale_types.ts b/src/plugins/vis_type_xy/public/editor/scale_types.ts new file mode 100644 index 0000000000000..c115b04ead5fd --- /dev/null +++ b/src/plugins/vis_type_xy/public/editor/scale_types.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 { i18n } from '@kbn/i18n'; + +import { ScaleType } from '../types'; + +export const getScaleTypes = () => [ + { + text: i18n.translate('visTypeXy.scaleTypes.linearText', { + defaultMessage: 'Linear', + }), + value: ScaleType.Linear, + }, + { + text: i18n.translate('visTypeXy.scaleTypes.logText', { + defaultMessage: 'Log', + }), + value: ScaleType.Log, + }, + { + text: i18n.translate('visTypeXy.scaleTypes.squareRootText', { + defaultMessage: 'Square root', + }), + value: ScaleType.SquareRoot, + }, +]; diff --git a/src/plugins/vis_type_xy/public/index.ts b/src/plugins/vis_type_xy/public/index.ts index 9af75ce9059e9..0739ad77a245b 100644 --- a/src/plugins/vis_type_xy/public/index.ts +++ b/src/plugins/vis_type_xy/public/index.ts @@ -17,11 +17,35 @@ * under the License. */ -import { PluginInitializerContext } from '../../../core/public'; import { VisTypeXyPlugin as Plugin } from './plugin'; export { VisTypeXyPluginSetup } from './plugin'; -export function plugin(initializerContext: PluginInitializerContext) { - return new Plugin(initializerContext); +// TODO: Remove when vis_type_vislib is removed +// https://github.com/elastic/kibana/issues/56143 +export { + CategoryAxis, + ThresholdLine, + ValueAxis, + Grid, + SeriesParam, + Dimension, + Dimensions, + ScaleType, + AxisType, + HistogramParams, + DateHistogramParams, +} from './types'; +export type { ValidationVisOptionsProps } from './editor/components/common/validation_wrapper'; +export { TruncateLabelsOption } from './editor/components/common/truncate_labels'; +export { getPositions } from './editor/positions'; +export { getScaleTypes } from './editor/scale_types'; +export { xyVisTypes } from './vis_types'; +export { getAggId } from './config/get_agg_id'; + +// Export common types +export * from '../common'; + +export function plugin() { + return new Plugin(); } diff --git a/src/plugins/vis_type_xy/public/plugin.ts b/src/plugins/vis_type_xy/public/plugin.ts index 667018c1e6e30..7425c5f7248ac 100644 --- a/src/plugins/vis_type_xy/public/plugin.ts +++ b/src/plugins/vis_type_xy/public/plugin.ts @@ -17,25 +17,30 @@ * under the License. */ -import { - CoreSetup, - CoreStart, - Plugin, - IUiSettingsClient, - PluginInitializerContext, -} from 'kibana/public'; - +import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public'; import { ChartsPluginSetup } from '../../charts/public'; +import { DataPublicPluginStart } from '../../data/public'; -export interface VisTypeXyDependencies { - uiSettings: IUiSettingsClient; - charts: ChartsPluginSetup; -} +import { createVisTypeXyVisFn } from './xy_vis_fn'; +import { + setDataActions, + setFormatService, + setThemeService, + setColorsService, + setTimefilter, + setUISettings, + setDocLinks, +} from './services'; +import { visTypesDefinitions } from './vis_types'; +import { LEGACY_CHARTS_LIBRARY } from '../common'; +import { xyVisRenderer } from './vis_renderer'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface VisTypeXyPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface VisTypeXyPluginStart {} /** @internal */ export interface VisTypeXyPluginSetupDependencies { @@ -48,40 +53,43 @@ export interface VisTypeXyPluginSetupDependencies { export interface VisTypeXyPluginStartDependencies { expressions: ReturnType; visualizations: VisualizationsStart; + data: DataPublicPluginStart; } -type VisTypeXyCoreSetup = CoreSetup; +type VisTypeXyCoreSetup = CoreSetup; /** @internal */ -export class VisTypeXyPlugin implements Plugin { - constructor(public initializerContext: PluginInitializerContext) {} - +export class VisTypeXyPlugin + implements + Plugin< + VisTypeXyPluginSetup, + VisTypeXyPluginStart, + VisTypeXyPluginSetupDependencies, + VisTypeXyPluginStartDependencies + > { public async setup( core: VisTypeXyCoreSetup, { expressions, visualizations, charts }: VisTypeXyPluginSetupDependencies ) { - // eslint-disable-next-line no-console - console.warn( - 'The visTypeXy plugin is enabled\n\n', - 'This may negatively alter existing vislib visualization configurations if saved.' - ); - const visualizationDependencies: Readonly = { - uiSettings: core.uiSettings, - charts, - }; - - const visTypeDefinitions: any[] = []; - const visFunctions: any = []; + if (!core.uiSettings.get(LEGACY_CHARTS_LIBRARY, true)) { + setUISettings(core.uiSettings); + setThemeService(charts.theme); + setColorsService(charts.legacyColors); - visFunctions.forEach((fn: any) => expressions.registerFunction(fn)); - visTypeDefinitions.forEach((vis: any) => - visualizations.createBaseVisualization(vis(visualizationDependencies)) - ); + [createVisTypeXyVisFn].forEach(expressions.registerFunction); + expressions.registerRenderer(xyVisRenderer); + visTypesDefinitions.forEach(visualizations.createBaseVisualization); + } return {}; } - public start(core: CoreStart, deps: VisTypeXyPluginStartDependencies) { - // nothing to do here + public start(core: CoreStart, { data }: VisTypeXyPluginStartDependencies) { + setFormatService(data.fieldFormats); + setDataActions(data.actions); + setTimefilter(data.query.timefilter.timefilter); + setDocLinks(core.docLinks); + + return {}; } } diff --git a/src/plugins/vis_type_xy/public/services.ts b/src/plugins/vis_type_xy/public/services.ts new file mode 100644 index 0000000000000..5a72759ecff6c --- /dev/null +++ b/src/plugins/vis_type_xy/public/services.ts @@ -0,0 +1,49 @@ +/* + * 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 { CoreSetup, DocLinksStart } from '../../../core/public'; +import { createGetterSetter } from '../../kibana_utils/public'; +import { DataPublicPluginStart } from '../../data/public'; +import { ChartsPluginSetup } from '../../charts/public'; + +export const [getUISettings, setUISettings] = createGetterSetter( + 'xy core.uiSettings' +); + +export const [getDataActions, setDataActions] = createGetterSetter< + DataPublicPluginStart['actions'] +>('xy data.actions'); + +export const [getFormatService, setFormatService] = createGetterSetter< + DataPublicPluginStart['fieldFormats'] +>('xy data.fieldFormats'); + +export const [getTimefilter, setTimefilter] = createGetterSetter< + DataPublicPluginStart['query']['timefilter']['timefilter'] +>('xy data.query.timefilter.timefilter'); + +export const [getThemeService, setThemeService] = createGetterSetter( + 'xy charts.theme' +); + +export const [getColorsService, setColorsService] = createGetterSetter< + ChartsPluginSetup['legacyColors'] +>('xy charts.color'); + +export const [getDocLinks, setDocLinks] = createGetterSetter('DocLinks'); diff --git a/src/plugins/vis_type_xy/public/to_ast.test.ts b/src/plugins/vis_type_xy/public/to_ast.test.ts new file mode 100644 index 0000000000000..678a9faaec585 --- /dev/null +++ b/src/plugins/vis_type_xy/public/to_ast.test.ts @@ -0,0 +1,60 @@ +/* + * 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 { Vis } from '../../visualizations/public'; +import { buildExpression } from '../../expressions/public'; +import { sampleAreaVis } from '../../vis_type_vislib/public/sample_vis.test.mocks'; + +import { toExpressionAst } from './to_ast'; +import { VisParams } from './types'; + +jest.mock('../../expressions/public', () => ({ + ...(jest.requireActual('../../expressions/public') as any), + buildExpression: jest.fn().mockImplementation(() => ({ + toAst: () => ({ + type: 'expression', + chain: [], + }), + })), +})); + +jest.mock('./to_ast_esaggs', () => ({ + getEsaggsFn: jest.fn(), +})); + +describe('xy vis toExpressionAst function', () => { + let vis: Vis; + + const params = { + timefilter: {}, + timeRange: {}, + abortSignal: {}, + } as any; + + beforeEach(() => { + vis = sampleAreaVis as any; + }); + + it('should match basic snapshot', () => { + toExpressionAst(vis, params); + const [, builtExpression] = (buildExpression as jest.Mock).mock.calls[0][0]; + + expect(builtExpression).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/vis_type_xy/public/to_ast.ts b/src/plugins/vis_type_xy/public/to_ast.ts new file mode 100644 index 0000000000000..c93dbe46dca0e --- /dev/null +++ b/src/plugins/vis_type_xy/public/to_ast.ts @@ -0,0 +1,94 @@ +/* + * 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 moment from 'moment'; + +import { VisToExpressionAst, getVisSchemas } from '../../visualizations/public'; +import { buildExpression, buildExpressionFunction } from '../../expressions/public'; +import { BUCKET_TYPES } from '../../data/public'; + +import { DateHistogramParams, Dimensions, HistogramParams, VisParams } from './types'; +import { visName, VisTypeXyExpressionFunctionDefinition } from './xy_vis_fn'; +import { XyVisType } from '../common'; +import { getEsaggsFn } from './to_ast_esaggs'; + +export const toExpressionAst: VisToExpressionAst = async (vis, params) => { + const schemas = getVisSchemas(vis, params); + const dimensions: Dimensions = { + x: schemas.segment ? schemas.segment[0] : null, + y: schemas.metric, + z: schemas.radius, + width: schemas.width, + series: schemas.group, + splitRow: schemas.split_row, + splitColumn: schemas.split_column, + }; + + const responseAggs = vis.data.aggs?.getResponseAggs().filter(({ enabled }) => enabled) ?? []; + + if (dimensions.x) { + const xAgg = responseAggs[dimensions.x.accessor] as any; + if (xAgg.type.name === BUCKET_TYPES.DATE_HISTOGRAM) { + (dimensions.x.params as DateHistogramParams).date = true; + const { esUnit, esValue } = xAgg.buckets.getInterval(); + (dimensions.x.params as DateHistogramParams).intervalESUnit = esUnit; + (dimensions.x.params as DateHistogramParams).intervalESValue = esValue; + (dimensions.x.params as DateHistogramParams).interval = moment + .duration(esValue, esUnit) + .asMilliseconds(); + (dimensions.x.params as DateHistogramParams).format = xAgg.buckets.getScaledDateFormat(); + } else if (xAgg.type.name === BUCKET_TYPES.HISTOGRAM) { + const intervalParam = xAgg.type.paramByName('interval'); + const output = { params: {} as any }; + await intervalParam.modifyAggConfigOnSearchRequestStart(xAgg, vis.data.searchSource, { + abortSignal: params.abortSignal, + }); + intervalParam.write(xAgg, output); + (dimensions.x.params as HistogramParams).interval = output.params.interval; + } + } + + const visConfig = { ...vis.params }; + + (dimensions.y || []).forEach((yDimension) => { + const yAgg = responseAggs[yDimension.accessor]; + const seriesParam = (visConfig.seriesParams || []).find( + (param: any) => param.data.id === yAgg.id + ); + if (seriesParam) { + const usedValueAxis = (visConfig.valueAxes || []).find( + (valueAxis: any) => valueAxis.id === seriesParam.valueAxis + ); + if (usedValueAxis?.scale.mode === 'percentage') { + yDimension.format = { id: 'percent' }; + } + } + }); + + visConfig.dimensions = dimensions; + + const visTypeXy = buildExpressionFunction(visName, { + type: vis.type.name as XyVisType, + visConfig: JSON.stringify(visConfig), + }); + + const ast = buildExpression([getEsaggsFn(vis), visTypeXy]); + + return ast.toAst(); +}; diff --git a/src/plugins/vis_type_xy/public/to_ast_esaggs.ts b/src/plugins/vis_type_xy/public/to_ast_esaggs.ts new file mode 100644 index 0000000000000..da8d11ac8340c --- /dev/null +++ b/src/plugins/vis_type_xy/public/to_ast_esaggs.ts @@ -0,0 +1,46 @@ +/* + * 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 { Vis } from '../../visualizations/public'; +import { buildExpression, buildExpressionFunction } from '../../expressions/public'; +import { + EsaggsExpressionFunctionDefinition, + IndexPatternLoadExpressionFunctionDefinition, +} from '../../data/public'; + +import { VisParams } from './types'; + +/** + * Get esaggs expressions function + * TODO: replace this with vis.data.aggs!.toExpressionAst(); + * https://github.com/elastic/kibana/issues/61768 + * @param vis + */ +export function getEsaggsFn(vis: Vis) { + return buildExpressionFunction('esaggs', { + index: buildExpression([ + buildExpressionFunction('indexPatternLoad', { + id: vis.data.indexPattern!.id!, + }), + ]), + metricsAtAllLevels: vis.isHierarchical(), + partialRows: false, + aggs: vis.data.aggs!.aggs.map((agg) => buildExpression(agg.toExpressionAst())), + }); +} diff --git a/src/plugins/vis_type_xy/public/types/config.ts b/src/plugins/vis_type_xy/public/types/config.ts new file mode 100644 index 0000000000000..ec73c0f6e3fc0 --- /dev/null +++ b/src/plugins/vis_type_xy/public/types/config.ts @@ -0,0 +1,130 @@ +/* + * 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 { + AxisSpec, + CustomTooltip, + Fit, + GridLineStyle, + Position, + Rotation, + SeriesScales, + TickFormatter, + TooltipProps, + TooltipValueFormatter, + YDomainRange, +} from '@elastic/charts'; + +import { Dimension, Scale, ThresholdLine } from './param'; + +export interface Column { + id: string | null; + name: string; +} + +export interface Aspect { + accessor: Column['id']; + aggType: string | null; + aggId: string | null; + column?: Dimension['accessor']; + title: Column['name']; + format?: Dimension['format']; + formatter?: TickFormatter; + params: Dimension['params']; +} + +export interface Aspects { + x: Aspect; + y: Aspect[]; + z?: Aspect; + series?: Aspect[]; +} + +export interface AxisGrid { + show?: boolean; + styles?: GridLineStyle; +} + +export interface TickOptions { + show?: boolean; + size?: number; + count?: number; + padding?: number; + formatter?: TickFormatter; + labelFormatter?: TickFormatter; + rotation?: number; + showDuplicates?: boolean; + integersOnly?: boolean; + showOverlappingTicks?: boolean; + showOverlappingLabels?: boolean; +} + +export type YScaleType = SeriesScales['yScaleType']; +export type XScaleType = SeriesScales['xScaleType']; + +export type ScaleConfig = Omit & { + type?: S; +}; + +export interface AxisConfig { + id: string; + groupId?: string; + position: Position; + ticks?: TickOptions; + show: boolean; + style: AxisSpec['style']; + scale: ScaleConfig; + domain?: YDomainRange; + title?: string; + grid?: AxisGrid; + integersOnly: boolean; +} + +export interface LegendOptions { + show: boolean; + position?: Position; +} + +export type ThresholdLineConfig = Omit & { + dash?: number[]; + groupId?: string; +}; + +export type TooltipConfig = Omit & { + detailedTooltip?: (headerFormatter?: TooltipValueFormatter) => CustomTooltip; +}; + +export interface VisConfig { + legend: LegendOptions; + tooltip: TooltipConfig; + xAxis: AxisConfig; + yAxes: Array>; + aspects: Aspects; + rotation: Rotation; + thresholdLine: ThresholdLineConfig; + orderBucketsBySum?: boolean; + showCurrentTime: boolean; + isTimeChart: boolean; + markSizeRatio: number; + showValueLabel: boolean; + enableHistogramMode: boolean; + fittingFunction?: Exclude; + detailedTooltip?: boolean; + isVislibVis?: boolean; +} diff --git a/src/plugins/vis_type_xy/public/types/constants.ts b/src/plugins/vis_type_xy/public/types/constants.ts new file mode 100644 index 0000000000000..f92c56e43413e --- /dev/null +++ b/src/plugins/vis_type_xy/public/types/constants.ts @@ -0,0 +1,67 @@ +/* + * 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 { $Values } from '@kbn/utility-types'; + +export const ChartMode = Object.freeze({ + Normal: 'normal' as const, + Stacked: 'stacked' as const, +}); +export type ChartMode = $Values; + +export const InterpolationMode = Object.freeze({ + Linear: 'linear' as const, + Cardinal: 'cardinal' as const, + StepAfter: 'step-after' as const, +}); +export type InterpolationMode = $Values; + +export const AxisType = Object.freeze({ + Category: 'category' as const, + Value: 'value' as const, +}); +export type AxisType = $Values; + +export const ScaleType = Object.freeze({ + Linear: 'linear' as const, + Log: 'log' as const, + SquareRoot: 'square root' as const, +}); +export type ScaleType = $Values; + +export const AxisMode = Object.freeze({ + Normal: 'normal' as const, + Percentage: 'percentage' as const, + Wiggle: 'wiggle' as const, + Silhouette: 'silhouette' as const, +}); +export type AxisMode = $Values; + +export const ThresholdLineStyle = Object.freeze({ + Full: 'full' as const, + Dashed: 'dashed' as const, + DotDashed: 'dot-dashed' as const, +}); +export type ThresholdLineStyle = $Values; + +export const ColorMode = Object.freeze({ + Background: 'Background' as const, + Labels: 'Labels' as const, + None: 'None' as const, +}); +export type ColorMode = $Values; diff --git a/src/plugins/vis_type_xy/public/types/index.ts b/src/plugins/vis_type_xy/public/types/index.ts new file mode 100644 index 0000000000000..791373def2018 --- /dev/null +++ b/src/plugins/vis_type_xy/public/types/index.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +export * from './constants'; +export * from './config'; +export * from './param'; +export * from './vis_type'; diff --git a/src/plugins/vis_type_xy/public/types/param.ts b/src/plugins/vis_type_xy/public/types/param.ts new file mode 100644 index 0000000000000..c8cd020dec03c --- /dev/null +++ b/src/plugins/vis_type_xy/public/types/param.ts @@ -0,0 +1,160 @@ +/* + * 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 { Fit, Position } from '@elastic/charts'; + +import { Style, Labels } from '../../../charts/public'; +import { SchemaConfig } from '../../../visualizations/public'; + +import { ChartType } from '../../common'; +import { + ChartMode, + AxisMode, + AxisType, + InterpolationMode, + ScaleType, + ThresholdLineStyle, +} from './constants'; + +export interface Scale { + boundsMargin?: number | ''; + defaultYExtents?: boolean; + max?: number | null; + min?: number | null; + mode?: AxisMode; + setYExtents?: boolean; + type: ScaleType; +} + +export interface CategoryAxis { + id: string; + labels: Labels; + position: Position; + scale: Scale; + show: boolean; + title: { + text?: string; + }; + type: AxisType; + /** + * Used only for heatmap, here for consistent types when used in vis_type_vislib + * + * remove with vis_type_vislib + * https://github.com/elastic/kibana/issues/56143 + */ + style: Partial